Bạn là kỹ sư tích hợp hệ thống thanh toán cao cấp. Hãy giúp tôi tích hợp "Merchant OpenAPI" dưới đây.

Yêu cầu tích hợp:
1. Viết mã tích hợp chạy được ngay bằng ngôn ngữ tôi chỉ định; nếu tôi chưa chỉ định, hãy hỏi trước (thường là Node.js / PHP / Python / Go / Java).
2. Thực hiện chữ ký `sign` đúng từng quy tắc (quan trọng với an toàn tiền, phải tuân thủ từng mục):
   - Thuật toán HMAC-SHA256, kết quả là hex thường;
   - Trường tham gia chữ ký = mọi trường tầng trên của thân yêu cầu, trừ `sign`, có giá trị không phải null;
   - Sắp theo tên trường tăng dần ASCII, nối `key=value` bằng `&`, rồi thêm `&secret=<khóa nghiệp vụ tương ứng>` ở cuối;
   - Trường có giá trị object/array (như `extra`) phải tuần tự hóa ổn định thành "JSON gọn, khóa tăng dần" rồi dùng chuỗi đó làm giá trị;
   - Thu hộ ký bằng api_secret_pay, chi hộ ký bằng api_secret_payout.
3. Cả thu hộ và chi hộ đều dùng `<BASE_URL>/v1/...`: `pay/create` (thu hộ) và `payout/create` (chi hộ) chỉ truyền `pay_method`, không dùng channel_code; chữ ký thống nhất body-only (không có tiền tố ràng buộc).
4. Bao gồm tạo đơn thu hộ `pay/create` và truy vấn `pay/query`; nếu tôi cần chi hộ, thêm `payout/create` và `payout/query` (luôn trên `<BASE_URL>/v1/...`). Với các endpoint này, ưu tiên ví dụ SDK chính thức trong tài liệu.
   Nếu bật xác minh trước lệnh chi, hãy tự triển khai endpoint theo tài liệu. Không dùng SDK chính thức. Thân yêu cầu chỉ có out_payout_no, payout_no, account_no và original_sign. Phản hồi: thân HTTP 200 phải đúng là văn bản ASCII thuần approve. Không được chứa khoảng trắng, xuống dòng, BOM, JSON hay nội dung khác. Nếu không, thông tin đơn không khớp và lệnh chi này bị từ chối. Hết thời gian chờ và lỗi mạng được truy vấn tối đa 3 lần.
5. Số tiền là số nguyên nhân 10000 (độ chính xác 0.0001, ví dụ 1.2345 gửi thành 12345); cấu trúc trả về thống nhất là `{ code, message, data }`, hãy xử lý các mã lỗi thường gặp trong tài liệu.
6. Địa chỉ endpoint và thông tin xác thực luôn dùng chỗ giữ chỗ, tôi sẽ tự thay (lấy Base URL tại "Security Center · OpenAPI" trong bảng điều khiển merchant): `<BASE_URL>`, `<YOUR_MERCHANT_NO>`, `<YOUR_API_KEY>`, `<YOUR_API_SECRET_PAY>`, `<YOUR_API_SECRET_PAYOUT>`. Phiên bản nằm trên đường dẫn (`/v1/...`).

Yêu cầu đầu ra: trước hết liệt kê các bước tích hợp, rồi đưa mã chạy được đầy đủ, và kèm một ví dụ chạy được tối thiểu của "tính chữ ký + tạo đơn".

Dưới đây là toàn bộ tài liệu API (endpoint, trường, quy tắc chữ ký và lối vào SDK chính thức đều nằm trong đó, hãy lấy đó làm chuẩn):

=== Tài liệu API bắt đầu ===
# Hướng dẫn tích hợp Merchant OpenAPI

- Base URL: Đăng nhập bảng điều khiển merchant "Trung tâm bảo mật · OpenAPI" để lấy (hướng dẫn này dùng `<BASE_URL>`, không kèm phiên bản). Phiên bản nằm trong đường dẫn: `<BASE_URL>/v1/...`.
- Phiên bản API: `v1` (phiên bản máy chủ 1.3.0; mọi phản hồi đều có header `X-Api-Version`).
- Ngày xuất: 20261109

> Lưu ý: Hướng dẫn này không chứa thông tin xác thực thật của merchant hoặc thông tin danh sách cho phép; hãy thay các chỗ giữ chỗ trước khi tích hợp.

## Thông tin merchant

- Tên merchant: <YOUR_MERCHANT_NAME>
- Mã merchant: <YOUR_MERCHANT_NO>

## SDK chính thức

[![npm](https://camo.githubusercontent.com/ac877a85fa44d1b06503a2e5d26b52556a5cd93de9f5c524dad77e36f2256d79/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f40626562656275732f6d65726368616e742d6f70656e6170692d73646b3f6c6162656c3d6e706d)](https://www.npmjs.com/package/@bebebus/merchant-openapi-sdk) [![PyPI](https://camo.githubusercontent.com/d9280c75614111cbdeb60cdfb93ee0da9daf0395dd872b8c76d97d8cca49c2ba/68747470733a2f2f696d672e736869656c64732e696f2f707970692f762f626562656275732d6d65726368616e742d6f70656e6170692d73646b3f6c6162656c3d50795049)](https://pypi.org/project/bebebus-merchant-openapi-sdk/) [![Packagist](https://camo.githubusercontent.com/827350ff74daa347de9a388649d0bcc2437a087eab4a9cdf8b4a1a394d45cb93/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626562656275732f6d65726368616e742d6f70656e6170692d73646b3f6c6162656c3d5061636b6167697374)](https://packagist.org/packages/bebebus/merchant-openapi-sdk) [![Go Reference](https://camo.githubusercontent.com/145b1f9d6e8723e28966424628bc3f7da84628faf7ccadd4495268b6a8af06b8/68747470733a2f2f706b672e676f2e6465762f62616467652f6769746875622e636f6d2f626562656275732f53444b2f676f2f76322e737667)](https://pkg.go.dev/github.com/bebebus/SDK/go/v2) [![License: MIT](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](https://github.com/bebebus/SDK/blob/main/LICENSE) [![OpenSSF Scorecard](https://camo.githubusercontent.com/c043a2e9361069f4776fd8cd78d8507b02692f0f5c6d6172bf9b43bac8d04074/68747470733a2f2f6170692e73636f7265636172642e6465762f70726f6a656374732f6769746875622e636f6d2f626562656275732f53444b2f6261646765)](https://scorecard.dev/viewer/?uri=github.com/bebebus/SDK)

SDK chính thức cho 5 ngôn ngữ: không phụ thuộc bên thứ ba (chỉ dùng thư viện chuẩn của từng ngôn ngữ), tích hợp sẵn ký yêu cầu HMAC-SHA256 / xác minh callback, và hai môi trường thử và chính thức; `baseUrl` thống nhất trỏ tới `<BASE_URL>/v1`.

| Ngôn ngữ | Cài đặt | Gói / Mã nguồn |
|------|------|------|
| Node.js | `npm install @bebebus/merchant-openapi-sdk` | [npm](https://www.npmjs.com/package/@bebebus/merchant-openapi-sdk) |
| Python | `pip install bebebus-merchant-openapi-sdk` | [PyPI](https://pypi.org/project/bebebus-merchant-openapi-sdk/) |
| PHP | `composer require bebebus/merchant-openapi-sdk` | [Packagist](https://packagist.org/packages/bebebus/merchant-openapi-sdk) |
| Go | `go get github.com/bebebus/SDK/go/v2` | [pkg.go.dev](https://pkg.go.dev/github.com/bebebus/SDK/go/v2) |
| Java | Từ mã nguồn (gói `cloud.cniia.openapi.sdk`, không có trên Maven) | [GitHub](https://github.com/bebebus/SDK/tree/main/java) |

- Kho lưu trữ và bản phát hành: https://github.com/bebebus/SDK (xem `SIGNING.md` để biết thuật toán ký chuẩn, và `INTERFACES.md` để biết yêu cầu/phản hồi ở mức trường).
- Với tích hợp mới, hãy trỏ `baseUrl` tới `<BASE_URL>/v1` và dùng SDK chính thức. Tạo đơn thu hộ và chi hộ đều chỉ truyền `pay_method` (không dùng channel_code).

### Tệp cấu hình phụ lục

Xem hoặc tải mẫu cấu hình theo từng ngôn ngữ (chỗ giữ chỗ — thay bằng thông tin xác thực từ bảng điều khiển merchant). Node.js / Java là JSON, PHP là mảng `return [...]`, Python là dict, còn Go là biến package.

| Tệp | Xem | Tải xuống |
|------|------|----------|
| Cấu hình Node.js (JSON) | [GitHub](https://github.com/bebebus/SDK/blob/main/nodejs/appendix/config.example.json) | [Tải xuống](https://raw.githubusercontent.com/bebebus/SDK/main/nodejs/appendix/config.example.json) |
| Cấu hình PHP (mảng) | [GitHub](https://github.com/bebebus/SDK/blob/main/php/appendix/config.example.php) | [Tải xuống](https://raw.githubusercontent.com/bebebus/SDK/main/php/appendix/config.example.php) |
| Cấu hình Python (dict) | [GitHub](https://github.com/bebebus/SDK/blob/main/python/appendix/config.example.py) | [Tải xuống](https://raw.githubusercontent.com/bebebus/SDK/main/python/appendix/config.example.py) |
| Cấu hình Go | [GitHub](https://github.com/bebebus/SDK/blob/main/go/appendix/config.example.go) | [Tải xuống](https://raw.githubusercontent.com/bebebus/SDK/main/go/appendix/config.example.go) |
| Cấu hình Java (JSON) | [GitHub](https://github.com/bebebus/SDK/blob/main/java/appendix/config.example.json) | [Tải xuống](https://raw.githubusercontent.com/bebebus/SDK/main/java/appendix/config.example.json) |

## Bắt đầu nhanh SDK

Mẫu SDK chính thức dưới đây tạo một đơn thu hộ. `pay_method` chọn phương thức thanh toán; số tiền là số nguyên bằng giá trị thực × 10000. Hãy thay chỗ giữ chỗ bằng thông tin xác thực và Base URL lấy từ bảng điều khiển merchant.

### Node.js

Cài đặt:

```bash
npm install @bebebus/merchant-openapi-sdk
```

```js
// <BASE_URL> is the OpenAPI Base URL shown in the merchant console (no version); set the SDK baseUrl to <BASE_URL>/v1.
import { Client, Config } from '@bebebus/merchant-openapi-sdk';

const client = new Client(new Config({
  merchantNo: '<YOUR_MERCHANT_NO>',
  apiKey: '<YOUR_API_KEY>',
  apiSecretPay: '<YOUR_API_SECRET_PAY>',
  apiSecretPayout: '<YOUR_API_SECRET_PAYOUT>',
  baseUrl: '<BASE_URL>/v1',
}));

const { data } = await client.payCreate({
  out_order_no: 'order-001',
  amount: 10000,
  currency: 'PHP',
  pay_method: 'gcash',
  country: 'PH',
  notify_url: 'https://merchant.example.com/api/notify/pay',
});
console.log(data.order_no, data.pay_url);
```

### Python

Cài đặt:

```bash
pip install bebebus-merchant-openapi-sdk
```

```python
# <BASE_URL> is the OpenAPI Base URL shown in the merchant console (no version); set the SDK baseUrl to <BASE_URL>/v1.
from openapi_sdk import Client, Config

client = Client(Config(
    merchant_no="<YOUR_MERCHANT_NO>",
    api_key="<YOUR_API_KEY>",
    api_secret_pay="<YOUR_API_SECRET_PAY>",
    api_secret_payout="<YOUR_API_SECRET_PAYOUT>",
    base_url="<BASE_URL>/v1",
))

data = client.pay_create(
    out_order_no="order-001",
    amount=10000,
    currency="PHP",
    pay_method="gcash",
    notify_url="https://merchant.example.com/api/notify/pay",
    country="PH",
)
print(data["order_no"], data.get("pay_url"))
```

### PHP

Cài đặt:

```bash
composer require bebebus/merchant-openapi-sdk
```

```php
<?php
// <BASE_URL> is the OpenAPI Base URL shown in the merchant console (no version); set the SDK baseUrl to <BASE_URL>/v1.
use Merchant\Openapi\Client;
use Merchant\Openapi\Config;

$client = new Client(new Config(
    merchantNo: '<YOUR_MERCHANT_NO>',
    apiKey: '<YOUR_API_KEY>',
    apiSecretPay: '<YOUR_API_SECRET_PAY>',
    apiSecretPayout: '<YOUR_API_SECRET_PAYOUT>',
    baseUrl: '<BASE_URL>/v1',
));

$data = $client->payCreate([
    'out_order_no' => 'order-001',
    'amount' => 10000,
    'currency' => 'PHP',
    'pay_method' => 'gcash',
    'country' => 'PH',
    'notify_url' => 'https://merchant.example.com/api/notify/pay',
]);
echo $data['order_no'];
```

### Go

Cài đặt:

```bash
go get github.com/bebebus/SDK/go/v2
```

```go
// <BASE_URL> is the OpenAPI Base URL shown in the merchant console (no version); set the SDK baseUrl to <BASE_URL>/v1.
package main

import (
	"context"
	"fmt"

	openapi "github.com/bebebus/SDK/go/v2"
)

func main() {
	client := openapi.NewClient(openapi.Config{
		MerchantNo:   "<YOUR_MERCHANT_NO>",
		APIKey:       "<YOUR_API_KEY>",
		SecretPay:    "<YOUR_API_SECRET_PAY>",
		SecretPayout: "<YOUR_API_SECRET_PAYOUT>",
		BaseURL:      "<BASE_URL>/v1",
	})
	resp, err := client.PayCreate(context.Background(), map[string]any{
		"out_order_no": "order-001",
		"amount":       10000,
		"currency":     "PHP",
		"pay_method":   "gcash",
		"country":      "PH",
		"notify_url":   "https://merchant.example.com/api/notify/pay",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.Data["order_no"], resp.Data["pay_url"])
}
```

### Java

Cài đặt:

```bash
# Not on Maven. Add java/src/main/java/cloud/cniia/openapi/sdk from the official repo to your sources.
javac --release 17 -encoding UTF-8 -d out $(find src/main/java -name '*.java')
```

```java
// <BASE_URL> is the OpenAPI Base URL shown in the merchant console (no version); set the SDK baseUrl to <BASE_URL>/v1.
import cloud.cniia.openapi.sdk.Client;
import cloud.cniia.openapi.sdk.Config;
import java.util.LinkedHashMap;
import java.util.Map;

Config config = Config.builder()
        .merchantNo("<YOUR_MERCHANT_NO>")
        .apiKey("<YOUR_API_KEY>")
        .apiSecretPay("<YOUR_API_SECRET_PAY>")
        .apiSecretPayout("<YOUR_API_SECRET_PAYOUT>")
        .baseUrl("<BASE_URL>/v1")
        .build();
Client client = new Client(config);

Map<String, Object> params = new LinkedHashMap<>();
params.put("out_order_no", "order-001");
params.put("amount", 10000L);
params.put("currency", "PHP");
params.put("pay_method", "gcash");
params.put("country", "PH");
params.put("notify_url", "https://merchant.example.com/api/notify/pay");
Map<String, Object> data = client.payCreate(params).dataAsMap();
System.out.println(data.get("order_no") + " " + data.get("pay_url"));
```

## Thu hộ: Tạo đơn (pay/create)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/pay/create`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/pay/create` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Tạo đơn thu hộ; trả về số đơn API và URL thanh toán / nội dung mã QR.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, out_order_no, amount, currency, pay_method, notify_url
- Trường tùy chọn: nonce, country, return_url, subject, remark, client_ip, extra

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| out_order_no | string | Có | Số đơn merchant (duy nhất trong phạm vi từng merchant; các lần gửi trùng trong vòng 90 ngày luôn bị từ chối và không bao giờ trả về đơn gốc; các đơn cũ hơn 90 ngày được lưu trữ và không còn được kiểm tra trùng, vì vậy không bao giờ tái sử dụng số đơn lịch sử) |
| amount | int | Có | Số tiền đơn (= giá trị thực × 10000 dưới dạng số nguyên, ví dụ truyền 12345 cho 1.2345). Phải là số nguyên JSON dương; không chấp nhận chuỗi (ví dụ "12345") hoặc số thập phân; dữ liệu đầu vào không hợp lệ trả về code=100001 (HTTP 200, với lỗi xác thực trường cụ thể trong message). |
| currency | string | Có | Loại tiền (ví dụ PHP/USDT; dùng để xác thực và định tuyến) |
| pay_method | string | Có | Phương thức thanh toán (chọn từ từ điển phương thức thanh toán, ví dụ gcash/maya; với tiền mã hóa hãy dùng tên chuỗi như trc20/erc20) |
| country | string\|null | Không | Mã ISO quốc gia (ví dụ PH; bắt buộc đối với tiền pháp định, tùy chọn đối với tiền mã hóa) |
| notify_url | string | Có | URL callback (dịch vụ gửi kết quả tới URL này khi đơn đạt trạng thái cuối) |
| return_url | string\|null | Không | URL trả về frontend (chuyển hướng về trang merchant sau khi thanh toán; có chuyển hướng hay không phụ thuộc vào phương thức thanh toán/kịch bản) |
| subject | string\|null | Không | Tiêu đề đơn (có thể dùng để hiển thị hoặc ghi chú đối soát) |
| remark | string\|null | Không | Ghi chú (có thể dùng cho ghi chú tùy chỉnh của merchant / đối soát) |
| client_ip | string\|null | Không | IP người dùng cuối (có thể dùng cho kiểm soát rủi ro; nếu bỏ qua, dịch vụ không thể lấy thông tin này) |
| extra | object\|null | Không | Trường mở rộng (trường cấp cao nhất, có tham gia chữ ký; object được tuần tự hóa bằng JSON ổn định). ⚠️ Khuyến nghị mạnh mẽ nên kèm thông tin người dùng cuối extra.customer (xem mục "Thông tin mở rộng và yêu cầu kênh" bên dưới) |
| extra.customer | object | Không | Thông tin người dùng cuối (tùy chọn). Nếu bỏ qua, yêu cầu không bị từ chối chỉ vì thiếu trường này; tuy nhiên một số phương thức thanh toán yêu cầu name/phone và đơn có thể thất bại nếu thiếu, nên khuyến nghị cung cấp. Dịch vụ xác thực định dạng và ánh xạ các trường (họ tên đầy đủ được tách thành first/last): hãy cung cấp họ tên đầy đủ (hoặc first_name+last_name), email và phone |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint pay dùng api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "amount": 10000,
  "currency": "PHP",
  "pay_method": "gcash",
  "country": "PH",
  "notify_url": "https://merchant.example.com/api/notify/pay",
  "return_url": "https://merchant.example.com/pay/result",
  "subject": "Tiêu đề đơn (tùy chọn)",
  "remark": "Ghi chú (tùy chọn)",
  "client_ip": "1.2.3.4",
  "extra": {
    "user_id": "u123456",
    "customer": {
      "first_name": "San",
      "last_name": "Zhang",
      "email": "user@example.com",
      "phone": "13800000000"
    }
  }
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Số đơn API (dùng cho truy vấn và đối soát tiếp theo) |
| out_order_no | string | Có | Số đơn merchant (trả về nguyên văn, để giúp merchant liên kết đơn nghiệp vụ của mình) |
| amount | int | Có | Số tiền đơn (= giá trị thực × 10000 dưới dạng số nguyên, ví dụ 12345 cho 1.2345) |
| currency | string | Có | Loại tiền (trả về nguyên văn loại tiền đã yêu cầu, ví dụ PHP/USDT) |
| pay_url | string\|null | Không | URL thanh toán (khi tạo đơn đồng bộ trả về code=0, ít nhất một trong pay_url, qrcode_content hoặc pay_params khác rỗng) |
| qrcode_content | string\|null | Không | Nội dung mã QR (tạo đơn đồng bộ trả về ít nhất một lối vào thanh toán) |
| pay_params | string\|null | Không | Chuỗi thanh toán native (tùy chọn): một số phương thức thanh toán trả về tham số khởi chạy ứng dụng native; khi pay_url rỗng và trường này khác rỗng, hãy dùng trường này để khởi chạy thanh toán trên client |
| expire_at | string\|null | Không | Thời điểm hết hạn (ISO8601; có thể null). Trường hợp chưa thanh toán và đã hết thời gian không còn được biểu diễn riêng thành trạng thái expired; đối ngoại được chuẩn hóa thành pending (đang xử lý) — hãy dựa vào kết quả truy vấn đơn. |
| status | string | Có | Trạng thái đơn: pending (đang xử lý) / success / failed. Chỉ success/failed là trạng thái cuối; trường hợp chưa thanh toán và đã hết thời gian không còn được trả về riêng dưới dạng expired mà được chuẩn hóa thành pending. |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "currency": "PHP",
    "pay_url": "https://pay.example.com/h5/P20250101000001",
    "qrcode_content": "https://pay.example.com/h5/P20250101000001",
    "pay_params": null,
    "expire_at": "2025-01-01T12:30:00Z",
    "status": "pending"
  }
}
```

### Ghi chú thêm

- Số tiền = giá trị thực × 10000 dưới dạng số nguyên, ví dụ truyền 12345 cho 1.2345, được truyền dưới dạng số nguyên để tránh sai số dấu phẩy động.
- Các lần gửi trùng với cùng out_order_no luôn bị từ chối (bất kể các tham số có khớp hay không; không bao giờ trả về đơn gốc): nếu yêu cầu bị hết thời gian chờ hoặc kết quả không xác định, trước tiên hãy xác nhận qua pay/query, và dùng out_order_no mới để tạo đơn mới. Việc kiểm tra tính duy nhất bao gồm các đơn trong vòng 90 ngày (các đơn cũ hơn được lưu trữ và không còn được kiểm tra), vì vậy luôn tạo một số mới, không bao giờ dùng lại (ví dụ kèm tiền tố ngày hoặc dùng UUID).
- [Thông tin người dùng cuối (được khuyến nghị mạnh mẽ)] Một số phương thức thanh toán yêu cầu họ tên và số điện thoại của người dùng cuối (email có thể là tùy chọn); thiếu chúng có thể khiến đơn thất bại. Hãy dùng các trường chung này: name — họ tên đầy đủ trong extra.customer.name (hoặc extra.customer.first_name+last_name tường minh); email — extra.customer.email (hoặc extra.email); phone — extra.customer.phone (hoặc extra.phone). Dịch vụ xác thực và ánh xạ các trường này, nên merchant không cần các tích hợp riêng cho từng phương thức thanh toán.
- [Chuỗi thanh toán native] Khi pay_url rỗng và pay_params khác rỗng (một số phương thức thanh toán chỉ trả về tham số khởi chạy native), hãy dùng pay_params để khởi chạy thanh toán trên client. Đối với dy-pay / dy-rep, pay_url là liên kết khởi chạy đã được chuyển đổi. Khi giá trị thượng nguồn đã là alipays:// hoặc http(s), cả hai trường đều là liên kết đó. Ngược lại, pay_params giữ nguyên văn từ alipay_sdk= đến &version=1.0, và pay_url nối văn bản đó, đã mã hóa URL đúng một lần, vào sau alipays://platformapi/startapp?appId=20000125&orderSuffix=. Hãy mở pay_url. Không mã hóa lại và không truyền pay_params cho SDK. Việc mở liên kết không phải là xác nhận đã ghi có cho merchant. Hãy dựa vào trạng thái success của truy vấn đơn.
- Trang đích thanh toán native không có khóa bí mật của merchant. Hãy gọi "Trang đích: Truy vấn chi tiết đơn" và "Trang đích: Truy vấn trạng thái đơn" bằng order_no được trả về tại đây (GET /api/cashier/pay/{order_no}, không cần chữ ký, và không nằm dưới /api/open/v1).

### Lỗi thường gặp

- 100101 Yêu cầu đã hết hạn
- 100102 IP không nằm trong danh sách cho phép
- 100104 Chữ ký không hợp lệ
- 100105 IP nằm trong danh sách chặn
- 100000 Tạo đơn thất bại (đơn không được tiếp nhận và đã được đặt thành failed theo thời gian thực)
- 300101 Số đơn merchant đã tồn tại; hãy truy vấn đơn hoặc dùng out_order_no mới
- 300404 Không có phương thức thanh toán khả dụng cho yêu cầu
- 100001 Xác thực tham số thất bại (ví dụ notify_url trỏ tới mạng nội bộ / giao thức không hợp lệ)
- 300402 Cấu hình phương thức thanh toán không khả dụng
- 300403 Chưa cấu hình tỷ lệ phí

## Thu hộ: Truy vấn đơn (pay/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/pay/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/pay/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn trạng thái đơn thu hộ theo số đơn API hoặc số đơn merchant.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, order_no/out_order_no (một trong hai)
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| order_no | string\|null | Không | Số đơn API (cung cấp trường này hoặc out_order_no; yêu cầu ít nhất một) |
| out_order_no | string\|null | Không | Số đơn merchant (cung cấp trường này hoặc order_no; yêu cầu ít nhất một) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint pay dùng api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "order_no": null
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Số đơn API |
| out_order_no | string | Có | Số đơn merchant |
| amount | int | Có | Số tiền đơn tham chiếu (= giá trị thực × 10000 dưới dạng số nguyên); chỉ để tham chiếu — ghi có theo actual_amount |
| actual_amount | int\|null | Không | Số tiền thực trả của người dùng cuối (căn cứ chính để ghi có thu hộ và đối soát; hãy ưu tiên dùng; null nếu chưa biết) |
| fee_amount | int\|null | Không | Phí (được tính lại từ số tiền thực trả; null nếu chưa biết) |
| net_amount | int\|null | Không | Số tiền ghi có ròng = số thực trả − phí (null nếu chưa biết) |
| currency | string | Có | Loại tiền |
| status | string | Có | Trạng thái đơn: pending (đang xử lý) / success / failed. Chỉ success/failed là trạng thái cuối; trường hợp chưa thanh toán và đã hết thời gian không còn được trả về riêng dưới dạng expired mà được chuẩn hóa thành pending. |
| channel_order_no | null | Không | Luôn trả về null. Hãy dùng order_no hoặc out_order_no cho truy vấn đơn và liên kết nghiệp vụ. |
| paid_at | string\|null | Không | Thời điểm thanh toán thành công (ISO8601; có thể trống khi chưa thành công) |
| notify_status | string | Có | Trạng thái gửi callback: pending/success/failed (không giống kết quả thanh toán) |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "actual_amount": 9500,
    "fee_amount": 500,
    "net_amount": 9000,
    "currency": "USDT",
    "status": "success",
    "channel_order_no": null,
    "paid_at": "2025-01-01T12:15:00Z",
    "notify_status": "success"
  }
}
```

### Ghi chú thêm

- Hãy cung cấp ít nhất một trong order_no/out_order_no.
- Các trạng thái cuối của status là success/failed; pending có nghĩa là đang xử lý (bao gồm chưa thanh toán và đã hết thời gian, được chuẩn hóa thành pending).
- Callback thu hộ chỉ được gửi cho merchant khi đơn thành công (success); các trạng thái không thành công như thất bại/hết thời gian chờ không được callback, vì vậy merchant cần biết kết quả cuối qua endpoint truy vấn đơn này.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 300301 Đơn không tồn tại

## Chi hộ: Tạo đơn (payout/create)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/create`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/create` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Tiếp nhận đơn chi hộ một cách đáng tin cậy và đóng băng số dư; code=0 không có nghĩa là kênh thượng nguồn đã tiếp nhận hoặc chi hộ cuối cùng đã thành công.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, out_payout_no, amount, currency, pay_method, notify_url, account_no
- Trường tùy chọn: nonce, country, account_name, bank_name, bank_code, remark, client_ip, extra

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| out_payout_no | string | Có | Số đơn chi hộ merchant (duy nhất trong phạm vi từng merchant; các lần gửi trùng trong vòng 90 ngày luôn bị từ chối và không bao giờ trả về đơn gốc; các đơn cũ hơn 90 ngày được lưu trữ và không còn được kiểm tra trùng, vì vậy không bao giờ tái sử dụng số đơn lịch sử) |
| amount | int | Có | Số tiền chi hộ (= giá trị thực × 10000 dưới dạng số nguyên, ví dụ truyền 12345 cho 1.2345). Phải là số nguyên JSON dương; không chấp nhận chuỗi (ví dụ "12345") hoặc số thập phân; dữ liệu đầu vào không hợp lệ trả về code=100001 (HTTP 200, với lỗi xác thực trường cụ thể trong message). |
| currency | string | Có | Loại tiền (ví dụ PHP/USDT; dùng để xác thực và định tuyến) |
| pay_method | string | Có | Phương thức thanh toán (chọn từ từ điển phương thức thanh toán, ví dụ gcash/maya; với tiền mã hóa hãy dùng tên chuỗi như trc20/erc20). Chi hộ không dùng channel_code / mã nhóm. |
| country | string\|null | Không | Mã ISO quốc gia (ví dụ PH; bắt buộc đối với tiền pháp định, tùy chọn đối với tiền mã hóa) |
| notify_url | string | Có | URL callback (dịch vụ gửi kết quả tới URL này khi đơn chi hộ đạt trạng thái cuối) |
| account_name | string\|null | Không | Tên người nhận / tên tài khoản (được diễn giải theo loại kênh; có thể null đối với loại địa chỉ on-chain) |
| account_no | string | Có | Tài khoản / địa chỉ người nhận (được diễn giải theo loại kênh; ví dụ số thẻ ngân hàng / địa chỉ on-chain) |
| bank_name | string\|null | Không | Tên ngân hàng / chuỗi (được diễn giải theo loại kênh; có thể null) |
| bank_code | string\|null | Không | Mã ngân hàng (tương ứng với code do endpoint "Truy vấn ngân hàng khả dụng" trả về; có thể bỏ qua khi không cần chọn ngân hàng; nếu cũng truyền bank_name, kết quả phân giải từ bank_code được ưu tiên) |
| remark | string\|null | Không | Ghi chú (có thể dùng cho ghi chú tùy chỉnh của merchant / đối soát) |
| client_ip | string\|null | Không | IP người dùng cuối (có thể dùng cho kiểm soát rủi ro) |
| extra | object\|null | Không | Trường mở rộng (có tham gia chữ ký; object được tuần tự hóa bằng JSON ổn định). ⚠️ Khuyến nghị mạnh mẽ nên kèm thông tin người dùng cuối extra.customer (xem mục "Thông tin mở rộng và yêu cầu kênh" bên dưới) |
| extra.customer | object | Không | Thông tin người dùng cuối (tùy chọn). Nếu bỏ qua, yêu cầu không bị từ chối chỉ vì thiếu trường này; tuy nhiên một số phương thức thanh toán yêu cầu name/phone và đơn có thể thất bại nếu thiếu, nên khuyến nghị cung cấp. Dịch vụ xác thực định dạng và ánh xạ các trường (họ tên đầy đủ được tách thành first/last): hãy cung cấp họ tên đầy đủ (hoặc first_name+last_name), email và phone |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint payout dùng api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_payout_no": "WD202501010001",
  "amount": 5000,
  "currency": "PHP",
  "pay_method": "gcash",
  "country": "PH",
  "notify_url": "https://merchant.example.com/api/notify/payout",
  "account_name": "San Zhang (tùy chọn)",
  "account_no": "09171234567",
  "bank_name": "Tùy chọn: điền tên ngân hàng đối với chi hộ bằng thẻ ngân hàng (có thể bỏ qua đối với loại ví như gcash)",
  "client_ip": "1.2.3.4",
  "remark": "Rút tiền merchant (tùy chọn)",
  "extra": {
    "user_id": "u123456",
    "customer": {
      "first_name": "San",
      "last_name": "Zhang",
      "email": "user@example.com",
      "phone": "13800000000"
    }
  }
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| payout_no | string | Có | Số đơn chi hộ API (dùng cho truy vấn và đối soát tiếp theo) |
| out_payout_no | string | Có | Số đơn chi hộ merchant (trả về nguyên văn, để giúp merchant liên kết nghiệp vụ của mình) |
| amount | int | Có | Số tiền chi hộ (= giá trị thực × 10000 dưới dạng số nguyên, ví dụ 12345 cho 1.2345) |
| currency | string | Có | Loại tiền (trả về nguyên văn loại tiền đã yêu cầu, ví dụ PHP/USDT) |
| status | string | Có | Trạng thái đơn: pending khi tạo (đang xử lý; chi hộ luôn đi vào luồng xử lý trước); success/failed là các trạng thái cuối. |
| review_status | string\|null | Không | Trạng thái phê duyệt chi hộ (khi bật phê duyệt chi hộ của merchant): pending (đang xét duyệt) / approved / rejected; null khi không cần phê duyệt. |
| fee_amount | int\|null | Không | Phí (có thể null) |
| freeze_amount | int\|null | Không | Số tiền đóng băng (có thể null; quy ước khuyến nghị amount + fee_amount) |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "amount": 5000,
    "currency": "PHP",
    "status": "pending",
    "review_status": "pending",
    "fee_amount": 20,
    "freeze_amount": 5020
  }
}
```

### Ghi chú thêm

- code=0 chỉ có nghĩa là nền tảng đã tiếp nhận đơn một cách đáng tin cậy và đã đóng băng số dư. Điều đó không có nghĩa là đơn đã được gửi tới kênh thượng nguồn/ngân hàng hoặc chi hộ đã thành công. Hãy xác định việc thượng nguồn tiếp nhận và kết quả cuối từ payout/query và thông báo bất đồng bộ.
- Khi bật xác minh trước lệnh chi, nền tảng gọi bất đồng bộ endpoint của merchant sau khi tạo đơn. Chỉ approve đúng mới tiếp tục duyệt hiện có. Hết thời gian chờ và lỗi mạng được truy vấn tối đa 3 lần. Xem Xác minh trước lệnh chi.
- out_payout_no là duy nhất trong phạm vi từng merchant; các lần gửi trùng luôn bị từ chối (bất kể các tham số có bằng nhau hay không; không bao giờ trả về đơn gốc): khi kết quả không xác định, trước tiên hãy xác nhận qua payout/query, và dùng số mới để gửi lại. Việc kiểm tra tính duy nhất bao gồm các đơn trong vòng 90 ngày (các đơn cũ hơn được lưu trữ và không còn được kiểm tra), vì vậy luôn tạo một số mới, không bao giờ dùng lại.
- [Thông tin người dùng cuối (được khuyến nghị mạnh mẽ)] Một số phương thức thanh toán yêu cầu họ tên và số điện thoại của người dùng cuối (email có thể là tùy chọn); thiếu chúng có thể khiến đơn thất bại. Hãy dùng các trường chung này: account_name cho tên người nhận hoặc tên tài khoản, hoặc họ tên đầy đủ trong extra.customer.name (hoặc extra.customer.first_name+last_name tường minh); email — extra.customer.email (hoặc extra.email); phone — extra.customer.phone (hoặc extra.phone). Dịch vụ xác thực và ánh xạ các trường này, nên merchant không cần các tích hợp riêng cho từng phương thức thanh toán.
- [Chi hộ loại ngân hàng] Khi pay_method là loại ngân hàng, bank_code là bắt buộc; giá trị dùng code do endpoint "Truy vấn ngân hàng khả dụng (payout/banks/query)" trả về; mã không hợp lệ trả về 300407.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 300201 Số đơn chi hộ merchant đã tồn tại; hãy truy vấn đơn hoặc dùng out_payout_no mới
- 300501 Số dư không đủ
- 300404 Không có phương thức thanh toán khả dụng cho yêu cầu
- 300403 Chưa cấu hình tỷ lệ phí
- 300401 Phương thức thanh toán không khả dụng / không tồn tại
- 100001 Xác thực tham số thất bại (ví dụ notify_url trỏ tới mạng nội bộ / giao thức không hợp lệ)
- 300402 Cấu hình phương thức thanh toán không khả dụng
- 300406 Thiếu tham số đơn bắt buộc (ví dụ bank_code)
- 300407 Mã ngân hàng không hợp lệ

## Chi hộ: Truy vấn đơn (payout/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn trạng thái đơn chi hộ theo số đơn chi hộ API hoặc số đơn chi hộ merchant.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, payout_no/out_payout_no (một trong hai)
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| payout_no | string\|null | Không | Số đơn chi hộ API (cung cấp trường này hoặc out_payout_no; yêu cầu ít nhất một) |
| out_payout_no | string\|null | Không | Số đơn chi hộ merchant (cung cấp trường này hoặc payout_no; yêu cầu ít nhất một) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint payout dùng api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| payout_no | string | Có | Số đơn chi hộ API |
| out_payout_no | string | Có | Số đơn chi hộ merchant |
| amount | int | Có | Số tiền chi hộ (= giá trị thực × 10000 dưới dạng số nguyên, ví dụ 12345 cho 1.2345) |
| currency | string | Có | Loại tiền |
| status | string | Có | Trạng thái đơn: pending (đang xử lý) / success / failed; chỉ success/failed là trạng thái cuối. |
| sub_state | string\|null | Không | Trạng thái con đang xử lý (đã chuẩn hóa): accepted / reviewing / processing có nghĩa là lệnh chi đã được tiếp nhận (có thể vẫn đang xếp hàng nội bộ và chưa được gửi tới thượng nguồn) / verifying có nghĩa là kết quả chi hộ đang được xác minh; tất cả đều chưa phải trạng thái cuối, và trạng thái cuối trả về null |
| channel_order_no | null | Không | Luôn trả về null. Hãy dùng payout_no hoặc out_payout_no cho truy vấn đơn và liên kết nghiệp vụ. |
| finished_at | string\|null | Không | Thời điểm hoàn tất (ISO8601; có thể trống khi chưa ở trạng thái cuối) |
| failed_reason | string\|null | Không | Lý do thất bại: chỉ trả về đối với đơn failed; thất bại nghiệp vụ an toàn đã được chính sách phê duyệt có thể trả về thông điệp nghiệp vụ của thượng nguồn, nếu không thì trả về thông điệp chung |
| notify_status | string | Có | Trạng thái gửi callback: pending/success/failed (không giống kết quả chi hộ) |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "amount": 5000,
    "currency": "USDT",
    "status": "success",
    "sub_state": null,
    "channel_order_no": null,
    "finished_at": "2025-01-01T13:00:00Z",
    "failed_reason": null,
    "notify_status": "success"
  }
}
```

### Ghi chú thêm

- Hãy cung cấp ít nhất một trong payout_no/out_payout_no.
- Các trạng thái cuối của status là success/failed; pending có nghĩa là đang xử lý (hãy tiếp tục chờ callback hoặc hỏi trạng thái bằng truy vấn đơn).
- sub_state là sự phân rã đã chuẩn hóa của trạng thái đang xử lý. processing có thể có nghĩa là nền tảng đã tiếp nhận lệnh chi nhưng đơn vẫn đang xếp hàng nội bộ và chưa được gửi tới kênh thượng nguồn/ngân hàng; không được coi đó là lỗi kênh. verifying có nghĩa là việc xác minh kết quả đang diễn ra. Chỉ dùng sub_state để hiển thị/xử lý sự cố, và hãy dựa quyết định nghiệp vụ vào trạng thái cuối của status (success/failed).

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 300301 Đơn không tồn tại

## Chi hộ: Truy vấn ngân hàng khả dụng (payout/banks/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/banks/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/banks/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn danh sách ngân hàng hiện đang khả dụng theo quốc gia + loại tiền + năng lực thanh toán (các giá trị hợp lệ của bank_code đối với đơn chi hộ loại ngân hàng).

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, pay_method, country, currency
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| pay_method | string | Có | Năng lực thanh toán (khác với pay_method trong payout/create, vốn xác định một nhóm thanh toán; khi truy vấn ngân hàng khả dụng thường đặt là bank) |
| country | string | Có | Mã ISO quốc gia (ví dụ PH) |
| currency | string | Có | Loại tiền (ví dụ PHP) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint payout dùng api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "pay_method": "gcash",
  "country": "PH",
  "currency": "PHP"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| banks | array | Có | Danh sách ngân hàng khả dụng [{code,name}]; code là giá trị hợp lệ của bank_code đối với đơn chi hộ. |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "banks": [
      {
        "code": "BDO",
        "name": "BDO Unibank"
      },
      {
        "code": "BPI",
        "name": "Bank of the Philippine Islands"
      },
      {
        "code": "GCASH",
        "name": "GCash"
      },
      {
        "code": "MAYA",
        "name": "Maya"
      }
    ]
  }
}
```

### Ghi chú thêm

- Danh sách ngân hàng khả dụng có thể thay đổi theo thời gian; hãy truy vấn sát trước khi tạo đơn hoặc dùng bộ nhớ đệm ngắn hạn.
- Khi truy vấn danh sách ngân hàng, năng lực thanh toán thường được cố định là pay_method=bank; các banks trả về có thể gồm ví điện tử (như GCASH/MAYA) làm điểm đến chi hộ, và bank_code có thể nhận code của chúng.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ

## Chi hộ: Truy vấn chứng từ thanh toán (payout/proof/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/proof/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/proof/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn URL chứng từ thanh toán của đơn đã chi hộ thành công (không phải mọi phương thức thanh toán/loại tiền đều cung cấp chứng từ).

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, payout_no/out_payout_no (một trong hai)
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| payout_no | string\|null | Không | Số đơn chi hộ API (cung cấp trường này hoặc out_payout_no; yêu cầu ít nhất một) |
| out_payout_no | string\|null | Không | Số đơn chi hộ merchant (cung cấp trường này hoặc payout_no; yêu cầu ít nhất một) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint payout dùng api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| payout_no | string | Có | Số đơn chi hộ API |
| out_payout_no | string | Có | Số đơn chi hộ merchant |
| proof_url | string | Có | URL chứng từ thanh toán (có thời hạn truy cập, xem expires_in; hãy dùng ngay và không lưu bền URL) |
| expires_in | int\|null | Không | Thời hạn hiệu lực của URL chứng từ (giây, do kênh quyết định, ví dụ 1800=30 phút; null có nghĩa là kênh không khai báo) |
| queried_at | string\|null | Không | Thời điểm truy vấn do dịch vụ trả về (có thể null; định dạng có thể không phải ISO8601) |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "proof_url": "https://proof.example.com/xxx.pdf",
    "expires_in": 1800,
    "queried_at": "2025-01-01 13:00:00"
  }
}
```

### Ghi chú thêm

- Chỉ đơn đã chi hộ thành công (status=success) mới truy vấn được chứng từ; đơn đang xử lý/failed trả về 300409.
- [Không khả dụng với khóa thử] Gọi endpoint này bằng khóa môi trường thử (env=test) luôn trả về 300408: chi hộ thử không cung cấp chứng từ thanh toán (không tra đơn, không gọi thượng nguồn).
- Không phải mọi kênh/loại tiền đều cung cấp chứng từ; một số kênh chỉ hỗ trợ truy vấn đơn trong vài ngày gần đây (vượt cửa sổ thì trả về 300409).
- URL chứng từ có thời hạn truy cập (expires_in giây); hãy tải tệp xuống và lưu lại thay vì lưu URL.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 300301 Đơn không tồn tại
- 300408 Kênh không hỗ trợ truy vấn chứng từ / không khả dụng với khóa thử
- 300409 Chứng từ tạm thời không khả dụng

## Chi hộ: Truy vấn ảnh biên lai (payout/receipt/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/receipt/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/receipt/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn/tạo ảnh biên lai thanh toán (PNG) cho đơn chi hộ. Chỉ đơn chi hộ có status=success mới tạo được; render-once lưu lại và dùng lại.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, payout_no/out_payout_no (một trong hai)
- Trường tùy chọn: nonce, lang, inline

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| payout_no | string\|null | Không | Số đơn chi hộ API (cung cấp trường này hoặc out_payout_no; yêu cầu ít nhất một) |
| out_payout_no | string\|null | Không | Số đơn chi hộ merchant (cung cấp trường này hoặc payout_no; yêu cầu ít nhất một) |
| lang | string\|null | Không | Ngôn ngữ biên lai (tùy chọn, enum: en / zh-CN / zh-TW; khi bỏ qua thì được tự suy ra từ quốc gia của đơn) |
| inline | int\|null | Không | Khi đặt là 1, trả về trực tiếp dữ liệu ảnh base64 (image_base64 + mime); khi bỏ qua hoặc đặt là 0, trả về liên kết tải xuống có chữ ký và có thời hạn (receipt_url) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint payout dùng api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "payout_no": null,
  "out_payout_no": "WD202501010001",
  "lang": "zh-CN",
  "inline": 0
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| payout_no | string | Có | Số đơn chi hộ API |
| out_payout_no | string | Có | Số đơn chi hộ merchant |
| lang | string | Có | Ngôn ngữ thực tế được dùng cho biên lai (en / zh-CN / zh-TW) |
| receipt_url | string\|null | Không | URL tải ảnh biên lai (đường dẫn tương đối từ gốc site, ví dụ /api/open/v1/payout/receipt/file?token=…). Hãy phân giải theo origin của host, ví dụ new URL(receipt_url, 'https://example.com'); không nối vào <BASE_URL>/v1. GET sẽ tải được; chỉ trả về khi inline=0 hoặc bỏ qua; liên kết sẽ hết hạn — hãy dùng ngay. |
| expires_in | int\|null | Không | Thời hạn hiệu lực của liên kết tải xuống (giây, ví dụ 3600=1 giờ; null có nghĩa là không khai báo thời hạn); chỉ có ý nghĩa khi inline=0 hoặc bỏ qua |
| mime | string\|null | Không | Loại MIME của ảnh (ví dụ image/png); chỉ trả về khi inline=1 |
| image_base64 | string\|null | Không | Dữ liệu ảnh biên lai được mã hóa Base64 (không kèm tiền tố data URI); chỉ trả về khi inline=1 |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "payout_no": "W20250101000001",
    "out_payout_no": "WD202501010001",
    "lang": "zh-CN",
    "receipt_url": "/api/open/v1/payout/receipt/file?token=eyJhbGciOiJIUzI1NiJ9...",
    "expires_in": 3600
  }
}
```

### Ghi chú thêm

- Chỉ đơn đã chi hộ thành công (status=success) mới tạo được biên lai; trạng thái không phải success trả về 300410.
- [Không khả dụng với khóa thử] Gọi endpoint này bằng khóa môi trường thử (env=test) luôn trả về 300410: chi hộ thử không cung cấp biên lai (không tra đơn, không tạo ảnh, không cấp token tải xuống).
- render-once: sau lần tạo đầu tiên, ảnh được lưu lại và dùng lại; truy vấn lặp lại với cùng tham số trả về trực tiếp kết quả đã lưu đệm.
- Khi bỏ qua lang, dịch vụ chọn ngôn ngữ theo quốc gia của đơn (ví dụ PH → en); giá trị tường minh sẽ ghi đè.
- Khi inline=1, thân phản hồi chứa trực tiếp ảnh Base64 (image_base64 + mime), phù hợp để xử lý ngay phía máy chủ; khi inline=0 (mặc định), trả về liên kết tải xuống receipt_url có chữ ký và có thời hạn, phù hợp để frontend chuyển hướng/nhúng.
- Liên kết tải xuống có thời hạn (expires_in giây); không lưu bền URL; để giữ lâu dài, hãy tải ảnh xuống và lưu lại vào kho của bạn.
- `receipt_url` là đường dẫn tương đối từ gốc site; hãy phân giải theo origin của host, và không nối vào OpenAPI Base URL.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 300301 Đơn không tồn tại
- 300410 Biên lai chi hộ tạm thời không khả dụng (đơn không phải success / không khả dụng với khóa thử)
- 300411 Tạo biên lai thất bại

## Chung: Truy vấn quốc gia và loại tiền (countries/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/countries/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/countries/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn từ điển quốc gia đang bật và loại tiền thông dụng của từng quốc gia (có thẩm quyền lúc chạy; phụ lục là bản chụp tĩnh).

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint này dùng api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| countries | array | Có | Danh sách quốc gia [{country,name,name_i18n,currencies}]; country là ISO 3166-1 alpha-2 (giá trị hợp lệ của trường country trên đơn); currencies là mảng các loại tiền thông dụng |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "countries": [
      {
        "country": "PH",
        "name": "<tên hiển thị zh-CN>",
        "name_i18n": {
          "zh-CN": "<tên hiển thị zh-CN>",
          "en-US": "Philippines"
        },
        "currencies": [
          "PHP"
        ]
      },
      {
        "country": "ID",
        "name": "<tên hiển thị zh-CN>",
        "name_i18n": {
          "zh-CN": "<tên hiển thị zh-CN>",
          "en-US": "Indonesia"
        },
        "currencies": [
          "IDR"
        ]
      }
    ]
  }
}
```

### Ghi chú thêm

- name là tên hiển thị tiếng Trung của nền tảng; để hiển thị tiếng Anh hãy dùng name_i18n["en-US"] (khi thiếu thì quay về tên tiếng Trung).
- Endpoint này là từ điển có thẩm quyền lúc chạy (phụ lục là bản chụp tĩnh); một quốc gia hiện có thể dùng hay không do pay-methods/query quyết định.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ

## Chung: Truy vấn phương thức thanh toán khả dụng (pay-methods/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/pay-methods/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/pay-methods/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn từ điển phương thức thanh toán khả dụng (các giá trị hợp lệ của pay_method/country khi tạo đơn).

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign
- Trường tùy chọn: nonce, country

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| country | string\|null | Không | Mã ISO quốc gia (ví dụ PH; khi bỏ qua thì trả về tất cả) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint này dùng api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "country": "PH"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| methods | array | Có | Danh sách phương thức thanh toán [{pay_method,name,country,currency,name_i18n,logo_svg}]; pay_method là giá trị hợp lệ cho các endpoint tạo đơn; name_i18n là object có các khóa zh-CN/en-US; logo_svg là SVG nguyên văn hoặc null |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "methods": [
      {
        "pay_method": "gcash",
        "name": "GCash",
        "country": "PH",
        "currency": "PHP",
        "name_i18n": {
          "zh-CN": "GCash",
          "en-US": "GCash"
        },
        "logo_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">...</svg>"
      },
      {
        "pay_method": "maya",
        "name": "Maya",
        "country": "PH",
        "currency": "PHP",
        "name_i18n": {
          "zh-CN": "Maya",
          "en-US": "Maya"
        },
        "logo_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">...</svg>"
      }
    ]
  }
}
```

### Ghi chú thêm

- Trả về từ điển phương thức thanh toán hiện đang khả dụng; một phương thức thanh toán hiện có thể dùng hay không do phản hồi thực tế của endpoint tạo đơn quyết định (trả về 300404 khi không có phương thức nào khả dụng).
- name là tên hiển thị tiếng Trung của nền tảng; để hiển thị tiếng Anh hãy dùng name_i18n["en-US"] (khi thiếu thì quay về tên tiếng Trung).
- logo_svg là SVG nguyên văn (không phải URL), mỗi phần tối đa 8KB, và có thể là null (chưa cấu hình hoặc tài sản đã ngừng dùng); không chứa script và không có tham chiếu bên ngoài. Hãy lưu đệm từ điển này ít nhất 1 giờ.
- Hãy kết xuất bằng `<img src="data:image/svg+xml;base64,…">` (trình duyệt không thực thi script và không tải tài nguyên bên ngoài trong <img>). Nếu bắt buộc phải nhúng SVG vào HTML DOM, hãy làm sạch theo chính sách bảo mật của bạn hoặc đặt trong một container sandbox; bảo đảm của nền tảng chỉ bao gồm tài sản đã được rà soát trong mã cùng với lớp bảo vệ lúc tải.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ

## Chung: Truy vấn số dư tài khoản (balance/query)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/balance/query`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/balance/query` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn số dư khả dụng/đóng băng của từng loại tiền trong tài khoản giao dịch của merchant.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign
- Trường tùy chọn: nonce, currency

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (lấy từ bảng điều khiển merchant) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| currency | string\|null | Không | Loại tiền (ví dụ PHP; khi bỏ qua thì trả về mọi loại tiền) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint này dùng api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "currency": "PHP"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| balances | array | Có | Danh sách số dư [{currency,available,frozen}]; số tiền = giá trị thực × 10000 dưới dạng số nguyên |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "balances": [
      {
        "currency": "PHP",
        "available": 12345600,
        "frozen": 50000
      }
    ]
  }
}
```

### Ghi chú thêm

- available là số dư khả dụng, frozen là số tiền đóng băng (bao gồm cả số đóng băng của chi hộ đang trên đường); cả hai đều là số nguyên theo đơn vị nhỏ nhất.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ

## Chung: Phiên bản dịch vụ (GET /version)

- Phương thức: `GET`
- Endpoint: `<BASE_URL>/v1/version`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/version` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Truy vấn phiên bản OpenAPI phía máy chủ (không cần xác thực); mọi phản hồi /api/open/ đều mang header X-Api-Version.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: Không có
- Trường tùy chọn: Không có

_Không có_

#### Ví dụ yêu cầu
```json
{}
```

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| major | string | Có | Phiên bản lớn (tiền tố đường dẫn, ví dụ v1) |
| version | string | Có | Phiên bản ngữ nghĩa (major.minor.patch) |
| base_path | string | Có | Tiền tố đường dẫn OpenAPI |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "major": "v1",
    "version": "1.3.0",
    "base_path": "/api/open/v1"
  }
}
```

### Ghi chú thêm

- Không cần xác thực; dùng để thăm dò trạng thái sống và xử lý sự cố canary phiên bản.

## Chi hộ: Tải tệp biên lai (GET /payout/receipt/file)

- Phương thức: `GET`
- Endpoint: `<BASE_URL>/v1/payout/receipt/file`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/payout/receipt/file` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Tải ảnh biên lai thanh toán chi hộ (PNG nhị phân). Endpoint này không được gọi trực tiếp; nó được suy ra từ receipt_url do "Truy vấn ảnh biên lai (payout/receipt/query)" trả về. receipt_url là đường dẫn tương đối từ gốc site — hãy phân giải theo origin của host rồi GET; không nối vào OpenAPI Base URL.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: token
- Trường tùy chọn: Không có

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| token | string | Có | Token tải xuống (tham số query). Do payout/receipt/query cấp (receipt_url trả về khi inline=0 / bỏ qua); nó nhúng số đơn chi hộ / merchant / ngôn ngữ / thời hạn và có giới hạn thời gian; không tự tạo hoặc lưu bền lâu dài. |

#### Ví dụ yêu cầu
```json
{}
```

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

_Không có_

#### Ví dụ phản hồi
```json
{}
```

### Ghi chú thêm

- Endpoint này trả về **ảnh nhị phân** (`Content-Type: image/png`, `content-disposition: inline`), không phải phong bì JSON `{code,message,data}`; tải xuống thành công sẽ nhận trực tiếp luồng byte PNG.
- Cách dùng: trước hết gọi "Truy vấn ảnh biên lai (payout/receipt/query)" (inline=0 hoặc bỏ qua) để nhận `receipt_url` (ví dụ /api/open/v1/payout/receipt/file?token=…). Đây là đường dẫn tương đối từ gốc site — hãy phân giải theo origin của host (`new URL(receipt_url, 'https://example.com')`); không nối vào <BASE_URL>/v1.
- **SDK chính thức không bọc endpoint này**: receipt_url là liên kết tải xuống có chữ ký và có thời hạn — hãy dùng HTTP client bất kỳ để tự GET (không cần ký/xác minh thêm); liên kết hết hạn (token không hợp lệ) trả về `100001`, nên chỉ cần gọi lại payout/receipt/query để lấy liên kết mới.
- Cách xác thực khác với các endpoint khác: endpoint này không dùng merchant_no/api_key/sign — chỉ xác thực bằng `token` trong query (do HMAC cấp), nên không cần và cũng không nên đính kèm các trường chữ ký.

### Lỗi thường gặp

- 100001 token không hợp lệ hoặc đã hết hạn (hãy gọi lại payout/receipt/query để lấy liên kết mới)
- 300410 Biên lai chi hộ tạm thời không khả dụng (đơn không phải success)

## Sandbox kiểm thử: Hoàn tất thủ công đơn thu hộ thử (pay/test/complete)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/pay/test/complete`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/pay/test/complete` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: [Chỉ khóa thử] Đẩy thủ công một đơn thu hộ thử tới trạng thái cuối (success/failed) để kiểm thử tích hợp mà không có luồng thanh toán thật; khóa chính thức bị từ chối.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, out_order_no/order_no (một trong hai), result
- Trường tùy chọn: nonce, actual_amount

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (phải là khóa môi trường **thử**; gọi bằng khóa chính thức sẽ bị từ chối) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| out_order_no | string\|null | Không | Số đơn merchant (cung cấp trường này hoặc order_no; yêu cầu ít nhất một) |
| order_no | string\|null | Không | Số đơn API (cung cấp trường này hoặc out_order_no; yêu cầu ít nhất một) |
| result | string | Có | Trạng thái cuối mong muốn: success hoặc failed |
| actual_amount | int\|null | Không | Số tiền thực trả của người dùng cuối (= giá trị thực × 10000 dưới dạng số nguyên, tùy chọn; chỉ có hiệu lực khi result=success — nếu không truyền thì mặc định bằng số tiền đơn; bị bỏ qua khi result=failed) |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint này dùng khóa thử api_secret_pay) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_order_no": "202501010001",
  "order_no": null,
  "result": "success",
  "actual_amount": 10000
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| ok | bool | Có | Thao tác có thành công hay không (luôn là true; thất bại được thể hiện qua code) |
| order_no | string | Có | Số đơn API |
| status | string | Có | Trạng thái cuối của đơn sau khi đẩy: success/failed |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "ok": true,
    "order_no": "P20250101000001",
    "status": "success"
  }
}
```

### Ghi chú thêm

- [Chỉ khóa thử] Chỉ khóa môi trường thử (env=test) mới gọi được endpoint này; khóa chính thức trả về 100001 (chỉ khóa thử mới gọi được endpoint hoàn tất thử).
- Chỉ áp dụng cho đơn thử: đơn tạo bằng khóa thử được cách ly vật lý khỏi đơn chính thức (kho lưu trữ thử riêng), đi theo luồng mock, không dịch chuyển tiền thật và không được tính vào thống kê. Gọi trên đơn không phải đơn thử, đơn không tồn tại hoặc đơn đã ở trạng thái cuối sẽ trả về 100001 (không tìm thấy đơn / đơn không phải đơn thử không thể hoàn tất thủ công / đơn đã ở trạng thái cuối).
- Sau khi đặt thành success, callback của merchant được đưa vào hàng đợi và gửi đi như thường lệ (sandbox "vẫn gửi callback"), hữu ích để kiểm thử xác minh chữ ký callback và logic ghi có; kết quả đã đẩy cũng truy vấn được qua pay/query.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 100001 Chỉ khóa thử / không tìm thấy đơn / không phải đơn thử / đơn đã ở trạng thái cuối (message mang lý do cụ thể)

## Sandbox kiểm thử: Hoàn tất thủ công đơn chi hộ thử (payout/test/complete)

- Phương thức: `POST`
- Endpoint: `<BASE_URL>/v1/merchant/payout/test/complete`
- Ví dụ URL đầy đủ: `https://<domain>/api/open/v1/merchant/payout/test/complete` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: [Chỉ khóa thử] Đẩy thủ công một đơn chi hộ thử tới trạng thái cuối (success/failed) để kiểm thử tích hợp mà không có luồng chi hộ thật; khóa chính thức bị từ chối.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: merchant_no, api_key, timestamp, sign, out_payout_no/payout_no (một trong hai), result
- Trường tùy chọn: nonce

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Số merchant (lấy từ bảng điều khiển merchant) |
| api_key | string | Có | API Key (phải là khóa môi trường **thử**; gọi bằng khóa chính thức sẽ bị từ chối) |
| timestamp | int | Có | Unix timestamp (giây), dùng để kiểm tra hết hạn (khuyến nghị cửa sổ ±300 giây) |
| nonce | string\|null | Không | Chuỗi ngẫu nhiên chống phát lại; khi không dùng, hãy bỏ qua hoặc truyền null (không truyền chuỗi rỗng) |
| out_payout_no | string\|null | Không | Số đơn chi hộ merchant (cung cấp trường này hoặc payout_no; yêu cầu ít nhất một) |
| payout_no | string\|null | Không | Số đơn chi hộ API (cung cấp trường này hoặc out_payout_no; yêu cầu ít nhất một) |
| result | string | Có | Trạng thái cuối mong muốn: success hoặc failed |
| sign | string | Có | Chữ ký (tính theo các quy tắc trong tài liệu; endpoint này dùng khóa thử api_secret_payout) |

#### Ví dụ yêu cầu
```json
{
  "merchant_no": "<YOUR_MERCHANT_NO>",
  "api_key": "<YOUR_API_KEY>",
  "timestamp": 1736073600,
  "nonce": "random-xyz",
  "sign": "<SIGN>",
  "out_payout_no": "WD202501010001",
  "payout_no": null,
  "result": "success"
}
```

> Ghi chú chữ ký: sign được tính trên "mọi trường cấp cao nhất của JSON yêu cầu, trừ sign"; các trường object/array được tuần tự hóa bằng JSON ổn định.

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| ok | bool | Có | Thao tác có thành công hay không (luôn là true; thất bại được thể hiện qua code) |
| payout_no | string | Có | Số đơn chi hộ API |
| status | string | Có | Trạng thái cuối của chi hộ sau khi đẩy: success/failed |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "ok": true,
    "payout_no": "W20250101000001",
    "status": "success"
  }
}
```

### Ghi chú thêm

- [Chỉ khóa thử] Chỉ khóa môi trường thử (env=test) mới gọi được endpoint này; khóa chính thức trả về 100001 (chỉ khóa thử mới gọi được endpoint hoàn tất thử). Việc hoàn tất thử chi hộ không có tham số actual_amount (chi hộ dùng số tiền đơn).
- Chỉ áp dụng cho đơn thử: đơn thử được cách ly vật lý khỏi đơn chính thức (kho lưu trữ thử riêng), không bao giờ dịch chuyển tiền thật (khoản đóng băng lúc tạo cũng không được giải đóng băng tại đây), không đi vào luồng thanh toán thật và không được tính vào thống kê. Gọi trên đơn không phải đơn thử, đơn không tồn tại hoặc đơn đã ở trạng thái cuối sẽ trả về 100001 (không tìm thấy đơn / đơn không phải đơn thử không thể hoàn tất thủ công / đơn đã ở trạng thái cuối).
- Khi bật xác minh trước lệnh chi, đơn thử phải nhận được approve từ endpoint của merchant trước thì endpoint này mới có thể mô phỏng success hoặc thất bại.
- Cả hai trạng thái cuối của chi hộ (success/failed) đều được đưa vào hàng đợi và gửi callback của merchant như thường lệ (sandbox "vẫn gửi callback"), hữu ích để kiểm thử xác minh chữ ký callback chi hộ; kết quả đã đẩy cũng truy vấn được qua payout/query.

### Lỗi thường gặp

- 100104 Chữ ký không hợp lệ
- 100001 Chỉ khóa thử / không tìm thấy đơn / không phải đơn thử / đơn đã ở trạng thái cuối (message mang lý do cụ thể)

## Chữ ký và trường chung

Các trường chung (nên đặt trong thân yêu cầu JSON): `merchant_no`, `api_key`, `timestamp`, `nonce` (tùy chọn), `sign`.
Thuật toán cơ sở: HMAC-SHA256. Sắp xếp mọi trường tham gia ký (không gồm sign) theo tên trường theo thứ tự ASCII tăng dần, ghép thành `key=value` và nối bằng `&`, rồi nối thêm `&secret=...` để được canonical body; chạy HMAC-SHA256 trên chuỗi đó và dùng kết quả thập lục phân chữ thường làm sign. Chuỗi cơ sở để ký chính là bản thân canonical body (body-only, không có tiền tố ràng buộc method+path).
Cửa sổ timestamp là **±300 giây** (yêu cầu có thời gian lệch so với máy chủ quá mức này sẽ trả về `100101`). Chống phát lại: khi cung cấp `nonce` thì loại trùng theo nonce, nếu không thì dùng phương án dự phòng "dấu vân tay chữ ký"; cả hai loại bản ghi loại trùng đều được giữ **600 giây** (bao trùm toàn bộ thời hạn hiệu lực của mọi yêu cầu trong cửa sổ), nghĩa là cùng một nonce / cùng một yêu cầu đã ký không được lặp lại trong vòng 600 giây (lặp lại sẽ trả về `100103`). Hãy tạo nonce duy nhất toàn cục cho mỗi yêu cầu (ví dụ UUID) và bảo đảm không dùng lại trong ít nhất 600 giây.

> Mẹo: các quy tắc ký ở trên đã được SDK chính thức triển khai sẵn — khi tích hợp, hãy gọi trực tiếp SDK, không cần tự viết phần ký hay tuần tự hóa ổn định.

## Quy ước số tiền

Mọi số tiền đều theo quy ước **số tiền = giá trị thực × 10000 dưới dạng số nguyên** (đơn vị hạch toán nhỏ nhất 0.0001), ví dụ truyền `12345` cho `1.2345`, truyền bằng số nguyên để tránh sai số dấu phẩy động. Quy ước này áp dụng cho mọi trường số tiền của yêu cầu, phản hồi và callback, không phân biệt loại tiền (đừng hiểu thành một đơn vị tiền tệ cụ thể).

## Sandbox khóa thử

**Khóa môi trường thử (env=test)** có sẵn để kiểm thử tích hợp: đơn thu hộ/chi hộ tạo bằng khóa thử là **đơn thử**, được **cách ly vật lý** khỏi đơn sản xuất (kho lưu trữ thử riêng), **đi tắt qua mock — không vào luồng thanh toán thật, không dịch chuyển tiền thật, và bị loại khỏi thống kê/đối soát** — trong khi **callback vẫn được gửi** (để bạn kiểm thử xác minh callback và logic ghi có). **Chi hộ thử không cung cấp chứng từ thanh toán/biên lai** (`payout/proof/query` trả về 300408, `payout/receipt/query` trả về 300410).
Vì đơn thử không bao giờ vào luồng thanh toán thật, có hai **endpoint hoàn tất thủ công** để bạn tự đẩy một đơn thử tới trạng thái cuối: `POST /merchant/pay/test/complete` (thu hộ, dùng api_secret_pay, actual_amount tùy chọn) và `POST /merchant/payout/test/complete` (chi hộ, dùng api_secret_payout), với `result` là `success`/`failed`; xem các mục endpoint tương ứng ở trên.
Khi bật xác minh trước lệnh chi, đơn chi hộ thử phải nhận được `approve` của merchant trước khi gọi `payout/test/complete`.
> Lưu ý: các endpoint hoàn tất thủ công **chỉ gọi được bằng khóa thử (env=test)**; khóa sản xuất luôn bị từ chối (trả về `100001`, message là "chỉ khóa thử mới gọi được endpoint hoàn tất thử"); và chúng chỉ áp dụng cho **đơn thử** — gọi chúng trên đơn sản xuất, đơn không tồn tại hoặc đơn đã ở trạng thái cuối cũng trả về `100001`. **Khóa sản xuất (env=prod) không có hành vi sandbox: đơn sản xuất không thể hoàn tất thủ công.**

## Xác minh trước lệnh chi

### Cấu hình và thời điểm

Cấu hình: trong bảng điều khiển merchant, mở Tích hợp OpenAPI → Xác minh trước lệnh chi, nhập URL HTTPS công khai và bật. Môi trường chính thức và thử nghiệm dùng chung URL và công tắc. Thay đổi chỉ áp dụng cho đơn tạo sau đó.

Thời điểm: sau khi payout/create chấp nhận đơn và đóng băng số dư chính thức, nền tảng POST JSON tới URL đó trong giai đoạn duyệt. Đây không phải thông báo trạng thái cuối và không đổi phản hồi payout/create. Khi tắt xác minh trước, luồng hiện có không đổi.

### Chi tiết yêu cầu

- Phương thức: `POST`
- Content-Type: `application/json`

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| out_payout_no | string | Có | Số đơn merchant |
| payout_no | string | Có | Số đơn nền tảng |
| account_no | string | Có | Tài khoản thụ hưởng |
| original_sign | string | Có | Chữ ký gốc của payout/create, đã loại bỏ khoảng trắng đầu và cuối |

#### Ví dụ yêu cầu

```json
{
  "out_payout_no": "merchant-order-no",
  "payout_no": "W...",
  "account_no": "account",
  "original_sign": "<original-payout-create-sign>"
}
```

### Chi tiết phản hồi

Phản hồi: thân HTTP 200 phải đúng là văn bản ASCII thuần approve. Không được chứa khoảng trắng, xuống dòng, BOM, JSON hay nội dung khác. Nếu không, thông tin đơn không khớp và lệnh chi này bị từ chối. Hết thời gian chờ và lỗi mạng được truy vấn tối đa 3 lần.

### Ghi chú thêm

approve chỉ nghĩa là qua lần xác minh này, không bỏ qua duyệt sẵn có. Đơn thử nghiệm chưa qua bước này thì không gọi được payout/test/complete.


## Chi tiết callback

Khi đơn đạt trạng thái cuối, dịch vụ gửi kết quả thu hộ/chi hộ bằng `POST application/json` tới `notify_url` đã cung cấp khi tạo đơn.
Callback cũng mang các trường chữ ký; merchant hãy xác minh chữ ký bằng khóa tương ứng của từng nghiệp vụ (pay/payout).

**Thời điểm callback thu hộ**: Thu hộ chỉ gửi callback tới merchant khi đơn thành công (`status=success`); các trạng thái không thành công như failed/hết thời gian chờ không kích hoạt callback, và merchant nên lấy kết quả cuối qua API truy vấn đơn (`pay/query`).
(Chi hộ không chịu ràng buộc này: chi hộ gửi callback ở cả hai trạng thái cuối `success/failed`.)

**Quy tắc xác nhận thành công**: Sau khi xử lý thành công, merchant phải trả về HTTP 200 với thân phản hồi là `success` hoặc `ok` (không phân biệt chữ hoa chữ thường);
Định dạng JSON cũng được chấp nhận: `{"success":true}` / `{"code":0}` / `{"message":"success"}` / `{"message":"ok"}`.
Nếu phản hồi không theo quy tắc này (kể cả HTTP 200 nhưng thân khác, như phản hồi rỗng hoặc trang lỗi HTML), callback được coi là thất bại,
sẽ được thử lại theo chính sách, và sẽ kích hoạt cảnh báo khi số lần thất bại liên tiếp đạt ngưỡng.

### Trường callback thu hộ

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Mã merchant |
| order_no | string | Có | Số đơn API |
| out_order_no | string | Có | Số đơn merchant |
| amount | int | Có | Số tiền đơn tham chiếu (= giá trị thực × 10000 dưới dạng số nguyên); chỉ để tham chiếu — ghi có theo actual_amount |
| actual_amount | int\|null | Không | Số tiền thực trả của người dùng cuối (căn cứ chính để ghi có thu hộ và đối soát; hãy ưu tiên dùng; null nếu chưa biết) |
| fee_amount | int\|null | Không | Phí (được tính lại từ số tiền thực trả; null nếu chưa biết) |
| net_amount | int\|null | Không | Số tiền ghi có ròng = số thực trả − phí (null nếu chưa biết) |
| currency | string | Có | Loại tiền |
| status | string | Có | Trạng thái chuẩn hóa; thu hộ chỉ gửi callback khi success, nên giá trị này luôn là success |
| channel_order_no | null | Không | Luôn trả về null; hãy dùng order_no hoặc out_order_no để liên kết đơn. Vì giá trị là null, trường này không tham gia chữ ký. |
| paid_at | string\|null | Không | Thời điểm thanh toán thành công (ISO8601; có thể trống nếu chưa thành công) |
| sign | string | Có | Chữ ký (tính bằng api_secret_pay đối với callback thu hộ) |

### Trường callback chi hộ

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Mã merchant |
| payout_no | string | Có | Số đơn chi hộ API |
| out_payout_no | string | Có | Số đơn chi hộ merchant |
| amount | int | Có | Số tiền chi hộ (= giá trị thực × 10000 dưới dạng số nguyên) |
| currency | string | Có | Loại tiền |
| status | string | Có | Trạng thái chuẩn hóa: success/failed (chi hộ gửi callback ở mọi trạng thái cuối) |
| fee_amount | int\|null | Không | Phí (null nếu chưa biết) |
| channel_order_no | null | Không | Luôn trả về null; hãy dùng payout_no hoặc out_payout_no để liên kết đơn. Vì giá trị là null, trường này không tham gia chữ ký. |
| finished_at | string\|null | Không | Thời điểm hoàn tất (ISO8601) |
| failed_reason | string\|null | Không | Lý do thất bại: thất bại nghiệp vụ an toàn đã được chính sách xác nhận có thể trả về nguyên văn thông điệp nghiệp vụ thượng nguồn; các trường hợp khác trả về thông điệp chung; null đối với trạng thái không phải failed |
| sign | string | Có | Chữ ký (tính bằng api_secret_payout đối với callback chi hộ) |

> Lưu ý: thân callback chi hộ **không** chứa `notify_status`. `notify_status` chỉ do API truy vấn đơn trả về và không bao giờ xuất hiện trong thân callback.

### Trường callback hoàn tiền

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| merchant_no | string | Có | Mã merchant |
| order_no | string\|null | Không | Số đơn API của đơn thu hộ nguồn đang được hoàn tiền (có thể là null trong trường hợp biên) |
| out_order_no | string\|null | Không | Số đơn merchant của đơn thu hộ nguồn đang được hoàn tiền (có thể là null trong trường hợp biên) |
| source_channel_order_no | null | Không | Luôn trả về null; callback hoàn tiền không cung cấp số đơn khác cho đơn thu hộ nguồn. Vì giá trị là null, trường này không tham gia chữ ký. |
| refund_no | string | Có | Số đơn hoàn tiền API |
| out_refund_no | string | Có | Số đơn hoàn tiền merchant |
| amount | int | Có | Số tiền hoàn tiền (= giá trị thực × 10000 dưới dạng số nguyên) |
| currency | string | Có | Loại tiền |
| status | string | Có | Trạng thái chuẩn hóa: success/failed (hoàn tiền gửi callback ở mọi trạng thái cuối, thống nhất với tập trạng thái cuối của chi hộ) |
| channel_order_no | null | Không | Luôn trả về null; hãy dùng refund_no hoặc out_refund_no để liên kết khoản hoàn tiền. Vì giá trị là null, trường này không tham gia chữ ký. |
| finished_at | string\|null | Không | Thời điểm hoàn tất (ISO8601) |
| failed_reason | string\|null | Không | Lý do thất bại (thông điệp chung đã khử nhạy cảm; được trả về khi failed, nếu không thì null) |
| sign | string | Có | Chữ ký (tính bằng api_secret_pay đối với callback hoàn tiền, dùng chung với thu hộ) |

> Ghi chú callback hoàn tiền: thân callback được **xác minh bằng `api_secret_pay`** (dùng chung với thu hộ, nên không cần khóa callback hoàn tiền riêng); thuật toán xác minh giống callback thu hộ/chi hộ (xem "Xác minh chữ ký callback" bên dưới). `channel_order_no` / `source_channel_order_no` luôn là `null` và **không tham gia chữ ký**. `refund_no` / `out_refund_no` là số đơn hoàn tiền, còn `order_no` / `out_order_no` liên kết tới đơn thu hộ gốc đang được hoàn tiền.

### Xác minh chữ ký callback

Thuật toán chữ ký callback **giống hệt** chữ ký yêu cầu (xem mục "Chữ ký và trường chung"):
1. Lấy mọi trường cấp cao nhất của JSON callback trừ `sign` và có giá trị khác `null`;
2. Sắp xếp theo tên trường theo thứ tự ASCII (code-point) tăng dần, rồi nối thành `key=value` bằng `&`;
3. Nối thêm `&secret=<corresponding secret>` ở cuối, tính HMAC-SHA256 trên chuỗi thô đó và xuất thập lục phân chữ thường;
4. So sánh với `sign` của thân callback (hãy dùng phép so sánh thời gian hằng, chẳng hạn `crypto.timingSafeEqual`, để chống tấn công theo thời gian).

Cách chọn khóa: **callback thu hộ dùng `api_secret_pay`, callback chi hộ dùng `api_secret_payout`**. Trường có giá trị `null` (chẳng hạn `channel_order_no`) **không tham gia chữ ký** (nhất quán với chữ ký yêu cầu, vốn bỏ qua null).

> **Lằn ranh đỏ về bảo mật (bắt buộc tuân thủ)**: (1) **Xác minh chữ ký trước, rồi mới xử lý nghiệp vụ và ghi có** — không bao giờ hành động theo nội dung callback trước khi xác minh; (2) Ghi có/đối soát bằng `actual_amount` (số tiền người dùng thực trả) và `net_amount` (số ghi có ròng = số thực trả − phí), **không bao giờ dùng `amount`** (chỉ là số tiền đơn tham chiếu); (3) Callback có thể được gửi nhiều hơn một lần do thử lại — hãy xử lý **idempotent** theo `out_order_no` / `out_payout_no`; thông báo trùng không được ghi có hai lần; (4) IP nguồn callback **không cố định** — **đừng hạn chế nguồn callback bằng danh sách cho phép IP; xác minh chữ ký là kiểm tra đáng tin duy nhất**.

### Thử lại / Hết thời gian chờ / Bảo mật

- **Hết thời gian chờ**: dịch vụ callback chờ phản hồi của merchant trong `8000ms`; không có phản hồi thì lần thử này bị tính là thất bại.
- **Thử lại**: tối đa `6` lần (tính cả lần đầu), khoảng backoff lần lượt xấp xỉ `1min / 2min / 5min / 10min / 30min / 60min` (vượt quá thì lấy mức cuối); nếu tới mức trần vẫn thất bại, tác vụ callback được đặt thành `failed`, và merchant có thể dùng API truy vấn đơn để lấy trạng thái cuối.
- **Không theo chuyển hướng**: dịch vụ callback không theo chuyển hướng 30x đối với `notify_url`.
- **Bảo vệ SSRF**: `notify_url` trỏ tới địa chỉ nội bộ / loopback / link-local sẽ bị chặn, bị tính là thất bại và **không thử lại** (địa chỉ sẽ không tự thay đổi); khi tạo đơn, `notify_url` dạng này cũng trả về `100001`.

### Quy ước trường và hợp đồng đối ngoại

- Số tiền callback theo cùng quy ước với yêu cầu: giá trị thực × 10000 dưới dạng số nguyên.
- `status` là trạng thái thống nhất do API trả về: thu hộ luôn là `success`; chi hộ là `success/failed`.

### Khác biệt thu hộ và chi hộ

- **Thời điểm kích hoạt**: thu hộ kích hoạt callback **chỉ khi `success`**; chi hộ kích hoạt ở **cả hai** trạng thái cuối `success/failed`.
- **Khác biệt trường**: thu hộ gồm `actual_amount` / `net_amount` / `paid_at`; chi hộ gồm `finished_at` / `failed_reason`.

## Chi tiết danh sách cho phép và chặn IP

- Danh sách cho phép mặc định tắt (mọi IP nguồn đều được phép truy cập OpenAPI).
- Khi chính sách được bật, khớp danh sách chặn sẽ bị từ chối ngay; khi có danh sách cho phép, chỉ được truy cập nếu khớp danh sách cho phép.
- Tài liệu xuất ra không chứa cấu hình danh sách cho phép thật của merchant; vui lòng xem trong bảng điều khiển merchant.

## Bảng mã lỗi

Mọi lỗi nghiệp vụ đều trả về **HTTP 200** kèm phong bì `{ code, message, data }`; **HTTP 4xx/5xx không bao giờ được dùng để biểu thị thất bại nghiệp vụ** (4xx/5xx ở tầng truyền tải chỉ cho biết sự cố mạng/gateway — hãy kiểm tra đường truyền hoặc thử lại). Danh sách Lỗi thường gặp của mỗi endpoint chỉ nêu các mục tần suất cao; bảng này là tham chiếu đầy đủ. Lỗi xác thực (`100xxx`) dùng chung cho mọi endpoint cần xác thực.

Tải mã lỗi：[Mã lỗi Node.js](https://raw.githubusercontent.com/bebebus/SDK/main/nodejs/appendix/error-codes.json)　[Mã lỗi PHP](https://raw.githubusercontent.com/bebebus/SDK/main/php/appendix/error-codes.php)　[Mã lỗi Python](https://raw.githubusercontent.com/bebebus/SDK/main/python/appendix/error-codes.py)　[Mã lỗi Go](https://raw.githubusercontent.com/bebebus/SDK/main/go/appendix/error-codes.go)　[Mã lỗi Java](https://raw.githubusercontent.com/bebebus/SDK/main/java/appendix/error-codes.json)

| Mã lỗi | Ý nghĩa | HTTP | Có thể thử lại | Ghi chú / Khắc phục sự cố |
|---|---|---|---|---|
| `100000` | Lỗi nghiệp vụ chung / mã thất bại xác thực thống nhất | 200 | Tùy trường hợp | Khi đơn không được tiếp nhận, trạng thái được đặt thành failed theo thời gian thực — không thử lại với cùng out_order_no; trong giai đoạn xác thực, thông báo được thống nhất là xác thực thất bại |
| `100001` | Kiểm tra tham số thất bại | 200 | Không | message mang thông tin lỗi kiểm tra của trường cụ thể (bằng tiếng Anh, ví dụ `"amount" must be a number`); amount phải là số nguyên JSON — chuỗi hoặc số thập phân đều trả về mã này. Sửa tham số rồi gửi lại |
| `100101` | Yêu cầu đã hết hạn | 200 | Có | timestamp nằm ngoài cửa sổ ±300s; hiệu chỉnh thời gian cục bộ rồi thử lại |
| `100102` | IP không nằm trong danh sách cho phép (message chứa merchant_no và IP của yêu cầu) | 200 | Không | Thêm IP nguồn vào danh sách cho phép trong bảng điều khiển merchant rồi thử lại; khóa chính thức và khóa thử dùng chung một danh sách cho phép |
| `100103` | Phát lại / yêu cầu trùng lặp | 200 | Không | nonce hoặc dấu vân tay chữ ký lặp lại trong vòng 300s; dùng nonce mới (hoặc đổi tham số) rồi thử lại |
| `100104` | Chữ ký không hợp lệ | 200 | Không | Kiểm tra khóa bí mật và thuật toán ký (pay dùng api_secret_pay, payout dùng api_secret_payout) |
| `100105` | IP nằm trong danh sách chặn | 200 | Không | Gỡ IP nguồn khỏi danh sách chặn rồi thử lại |
| `100106` | Quá nhiều lần xác thực thất bại (giới hạn tần suất) | 200 | Có | Được kích hoạt khi cùng merchant_no + IP nguồn thất bại xác thực 60 lần trong 60s; chờ khoảng 60s rồi thử lại |
| `100108` | Quá nhiều lượt đọc đơn trên trang đích | 429 | Có | Cùng một IP gọi endpoint chi tiết đơn hoặc endpoint trạng thái của trang đích hơn 6000 lần trong 60 giây. HTTP 429, header Retry-After được đặt bằng số giây của cửa sổ; hãy giảm tần suất hỏi trạng thái rồi thử lại |
| `200002` | Tài khoản dịch vụ đã bị vô hiệu hóa | 200 | Không | Liên hệ nhà cung cấp dịch vụ |
| `210002` | Merchant đã bị vô hiệu hóa | 200 | Không | Liên hệ nhà cung cấp dịch vụ |
| `300101` | Số đơn merchant đã tồn tại | 200 | Không | out_order_no trùng lặp luôn bị từ chối (bất kể tham số; đơn gốc không bao giờ được trả về). Hãy truy vấn đơn hoặc dùng số mới. |
| `300201` | Số đơn chi hộ của merchant đã tồn tại | 200 | Không | out_payout_no trùng lặp luôn bị từ chối (bất kể tham số; đơn gốc không bao giờ được trả về). Hãy truy vấn đơn hoặc dùng số mới. |
| `300301` | Không tìm thấy đơn | 200 | Không | Kiểm tra số đơn (order_no / out_order_no / payout_no / out_payout_no) |
| `300302` | Dữ liệu đơn tạm thời không khả dụng | 500 | Có | Chỉ được trả về bởi endpoint chi tiết đơn của trang đích khi không đọc được đơn cục bộ, HTTP 500. Hãy thử lại sau; để lấy kết quả thanh toán, hãy hỏi trạng thái bằng endpoint trạng thái |
| `300304` | Tạm thời không thể tạo đơn (chưa lưu dữ liệu nào; hãy thử lại với cùng số đơn merchant) | 503 | Có | Đã cạn ngân sách thử lại insert-first hoặc thiếu phân mảnh phiên bản risk-config — fail-closed trước INSERT; HTTP 503. Hãy thử lại sau với cùng out_order_no / out_payout_no; không đổi số, nếu không sẽ có thể tạo đơn trùng |
| `300401` | Phương thức thanh toán không khả dụng / không tồn tại | 200 | Không | Phương thức thanh toán không khả dụng hoặc không tồn tại |
| `300402` | Cấu hình phương thức thanh toán không khả dụng | 200 | Có | Không có cấu hình hợp lệ nào khớp với yêu cầu; hãy đổi tham số hoặc thử lại sau |
| `300403` | Tỷ lệ phí chưa được cấu hình | 200 | Không | Liên hệ nhà cung cấp dịch vụ để cấu hình tỷ lệ phí, rồi thử lại |
| `300404` | Không có phương thức thanh toán khả dụng cho yêu cầu | 200 | Có | country + currency + pay_method không khớp với nhóm đang bật nào; hãy kiểm tra tham số hoặc liên hệ bộ phận vận hành để bật một nhóm |
| `300405` | Thiếu thông tin bổ sung bắt buộc (extra.customer) | 200 | Không | Hãy điền các trường được liệt kê trong `data.missing_fields` thuộc extra.customer |
| `300406` | Thiếu tham số tạo đơn bắt buộc (ví dụ bank_code) | 200 | Không | Điền trường bắt buộc của tuyến đó rồi thử lại |
| `300407` | Mã ngân hàng không hợp lệ | 200 | Không | Dùng mã được trả về bởi Truy vấn ngân hàng khả dụng (payout/banks/query) |
| `300408` | Phương thức thanh toán không hỗ trợ truy vấn chứng từ chi hộ | 200 | Không | Phương thức thanh toán không hỗ trợ truy vấn chứng từ |
| `300409` | Chứng từ chi hộ tạm thời không khả dụng | 200 | Có | Đơn không thành công / chứng từ chưa sẵn sàng / ngoài cửa sổ truy vấn; hãy xác nhận thành công rồi thử lại trong cửa sổ |
| `300410` | Biên lai chi hộ tạm thời không khả dụng (đơn không phải success) | 200 | Có | Thử lại sau khi chi hộ thành công |
| `300411` | Tạo biên lai thất bại | 200 | Có | Lỗi kết xuất; thử lại sau |
| `300501` | Số dư không đủ | 200 | Có | Số dư khả dụng < số tiền bị đóng băng (amount + fee); hãy nạp tiền rồi thử lại |

## Giới hạn tần suất và gợi ý hỏi trạng thái

- **Giới hạn tần suất chỉ áp dụng cho các lần xác thực thất bại**: khi cùng một `merchant_no` + IP nguồn tích lũy 60 lần thất bại xác thực trong 60 giây, các yêu cầu tiếp theo bị từ chối với `100106` và được khôi phục khi cửa sổ trượt (~60s). Các yêu cầu nghiệp vụ đã xác thực thành công **không có trần QPS cứng**. Lượt đọc đơn trên trang đích có hạn mức riêng: hơn 6000 lần gọi từ cùng một IP trong 60 giây trả về `100108` (HTTP 429).
- **Không thử lại các lần xác thực thất bại khi không có backoff** (lỗi chữ ký, lệch đồng hồ, v.v.), nếu không sẽ dễ gặp `100106`; hãy sửa nguyên nhân trước khi thử lại.
- **Gợi ý hỏi trạng thái khi truy vấn**: trước hết dựa vào callback cho trạng thái cuối; chỉ hỏi API truy vấn khi không nhận được callback, dùng backoff theo cấp số nhân (ví dụ độ trễ lần đầu 3–5s, sau đó 5s → 15s → 30s → 60s cho đến trạng thái cuối hoặc một giới hạn hợp lý). Tránh hỏi trạng thái rỗng với tần suất cao đối với đơn chưa ở trạng thái cuối.

## Lưu ý tích hợp

- **Ghi có theo `actual_amount`**: thu hộ ghi có và đối soát theo số tiền người dùng **thực trả**; hãy dùng `actual_amount` (số thực trả) và `net_amount` (số ghi có ròng = số thực trả − phí) từ callback / truy vấn, không dùng `amount`, vốn chỉ là số tiền đơn tham chiếu và không được dùng để ghi có.
- **Giới hạn số tiền**: số tiền tối thiểu/tối đa của mỗi `pay_method` được **kiểm tra theo thời gian thực** và không được công bố dưới dạng giá trị cố định; đơn vượt khoảng sẽ trả về mã lỗi tương ứng (ví dụ `300402` / `300404`). Không ghi cứng khoảng số tiền trên client.
- **Thời hạn hiệu lực đơn**: hãy dùng `expire_at` do endpoint tạo đơn trả về, thay vì ghi cứng một thời lượng cố định. Hết thời gian chưa thanh toán được chuẩn hóa thành `pending`; hãy dựa vào kết quả truy vấn đơn.
- **Nguồn callback**: IP egress của callback không cố định; không được giới hạn nguồn callback bằng danh sách cho phép IP — hãy dùng **xác minh chữ ký** làm kiểm tra đáng tin cậy duy nhất (xem Xác minh chữ ký callback và các lằn ranh đỏ bảo mật ở trên).

## Trang đích: Truy vấn chi tiết đơn (GET /api/cashier/pay/{order_no})

- Phương thức: `GET`
- Endpoint: `https://<domain>/api/cashier/pay/{order_no}`
- Ví dụ URL đầy đủ: `https://<domain>/api/cashier/pay/{order_no}` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Đọc đơn thu hộ cho trang đích thanh toán native. Các trường khớp với data của pay/create thành công. Không cần chữ ký.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: order_no
- Trường tùy chọn: Không có

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Tham số đường dẫn. order_no do pay/create trả về. Đơn chính thức bắt đầu bằng P, đơn thử bắt đầu bằng TP. Không chấp nhận out_order_no của merchant và số đơn chi hộ. |

#### Ví dụ yêu cầu
```json
{}
```

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Số đơn API |
| out_order_no | string | Có | Số đơn merchant (chỉ trả về; không thể dùng để truy vấn endpoint này) |
| amount | int | Có | Số tiền đơn (= giá trị thực × 10000 dưới dạng số nguyên) |
| currency | string | Có | Loại tiền |
| pay_url | string\|null | Không | URL thanh toán. Cùng trường với phản hồi pay/create thành công. |
| qrcode_content | string\|null | Không | Nội dung mã QR |
| pay_params | string\|null | Không | Chuỗi thanh toán native; chuỗi rỗng cũng được trả về là null |
| expire_at | string\|null | Không | Thời điểm hết hạn (ISO8601, UTC, có mili giây). Sau khi hết hạn, status vẫn là pending; hãy dùng trường này để quyết định đơn đã hết hạn hay chưa. |
| status | string | Có | Trạng thái đơn: pending (đang xử lý) / success / failed. Chỉ success và failed là trạng thái cuối. |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "currency": "PHP",
    "pay_url": "https://pay.example.com/h5/P20250101000001",
    "qrcode_content": "https://pay.example.com/h5/P20250101000001",
    "pay_params": null,
    "expire_at": "2025-01-01T12:30:00.000Z",
    "status": "pending"
  }
}
```

### Ghi chú thêm

- Không cần chữ ký merchant và không có thân yêu cầu. Bản thân số đơn là thông tin xác thực truy cập.
- Đường dẫn là `/api/cashier/pay/{order_no}` trên origin của site. Không nối vào `<BASE_URL>/v1`.
- Trang trình duyệt có thể gọi cross-origin: phản hồi phản ánh Origin của yêu cầu và không dùng cookie.
- Endpoint này chỉ đọc đơn cục bộ và không truy vấn kênh thượng nguồn. Hãy hỏi trạng thái kết quả thanh toán qua "Trang đích: Truy vấn trạng thái đơn".
- Khi máy chủ trang đích chuyển tiếp lời gọi, IP egress của máy chủ đó phải được đăng ký trong danh sách cho phép IP máy chủ của trang đích đó, nếu không tầng mạng sẽ chặn.

### Lỗi thường gặp

- 300301 Đơn không tồn tại (HTTP 200; số đơn không hợp lệ và đơn không tìm thấy trả về cùng một phản hồi)
- 100108 Quá nhiều yêu cầu (HTTP 429, kèm header Retry-After)
- 300302 Dữ liệu đơn tạm thời không khả dụng (HTTP 500)
- 100000 Truy vấn thất bại (HTTP 200)

## Trang đích: Truy vấn trạng thái đơn (GET /api/cashier/pay/{order_no}/status)

- Phương thức: `GET`
- Endpoint: `https://<domain>/api/cashier/pay/{order_no}/status`
- Ví dụ URL đầy đủ: `https://<domain>/api/cashier/pay/{order_no}/status` (thay `<domain>` bằng tên miền trong bảng điều khiển merchant, mục "Security Center · OpenAPI")
- Mục đích: Hỏi trạng thái đơn và thời điểm hết hạn cho trang đích thanh toán native. Không cần chữ ký. Không trả về liên kết thanh toán.

### Chi tiết yêu cầu

- Content-Type: `application/json`
- Trường bắt buộc: order_no
- Trường tùy chọn: Không có

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Tham số đường dẫn. Giống endpoint chi tiết đơn: đơn chính thức bắt đầu bằng P, đơn thử bắt đầu bằng TP. |

#### Ví dụ yêu cầu
```json
{}
```

### Trường phản hồi và ví dụ

- Cấu trúc phản hồi thống nhất: `{ code, message, data }`; thất bại nghiệp vụ thường vẫn trả về HTTP 200.

| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| order_no | string | Có | Số đơn API |
| out_order_no | string | Có | Số đơn merchant (chỉ trả về; không thể dùng để truy vấn endpoint này) |
| amount | int | Có | Số tiền đơn (= giá trị thực × 10000 dưới dạng số nguyên) |
| currency | string | Có | Loại tiền |
| expire_at | string\|null | Không | Thời điểm hết hạn (ISO8601, UTC, có mili giây). Sau khi hết hạn, status vẫn là pending. |
| status | string | Có | Trạng thái đơn: pending (đang xử lý) / success / failed. Chỉ success và failed là trạng thái cuối. |

#### Ví dụ phản hồi
```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_no": "P20250101000001",
    "out_order_no": "202501010001",
    "amount": 10000,
    "currency": "PHP",
    "expire_at": "2025-01-01T12:30:00.000Z",
    "status": "pending"
  }
}
```

### Ghi chú thêm

- Không cần chữ ký merchant và không có thân yêu cầu. Dùng cho việc hỏi trạng thái của trang đích; hãy giữ khoảng cách từ 2 giây trở lên.
- Đường dẫn là `/api/cashier/pay/{order_no}/status` trên origin của site. Không nối vào `<BASE_URL>/v1`.
- Đơn chính thức chưa ở trạng thái cuối được đối soát với kênh thượng nguồn tối đa khoảng một lần mỗi 5 giây. Nếu đối soát thất bại, trạng thái cục bộ vẫn được trả về, nên việc hỏi trạng thái không thất bại. Đơn thử không được đối soát với thượng nguồn.
- Hãy xác định việc hết hạn theo expire_at. Không chờ một trạng thái expired.
- Khi máy chủ trang đích chuyển tiếp lời gọi, IP egress của máy chủ đó phải được đăng ký trong danh sách cho phép IP máy chủ của trang đích đó, nếu không tầng mạng sẽ chặn.

### Lỗi thường gặp

- 300301 Đơn không tồn tại (HTTP 200; số đơn không hợp lệ và đơn không tìm thấy trả về cùng một phản hồi)
- 100108 Quá nhiều yêu cầu (HTTP 429, kèm header Retry-After)
- 100000 Truy vấn thất bại (HTTP 200)

## Phụ lục: Mã ISO quốc gia

Bảng này là ảnh chụp tĩnh; hãy gọi `countries/query` để lấy từ điển có thẩm quyền lúc chạy. Trường `country` dùng **ISO 3166-1 alpha-2**. Bảng dưới đây là từ điển quốc gia của nền tảng; một quốc gia hiện có dùng được hay không do `pay-methods/query` quyết định. Với crypto, có thể bỏ qua `country`.

Tải dữ liệu：[Dữ liệu Node.js](https://raw.githubusercontent.com/bebebus/SDK/main/nodejs/appendix/catalog.json)　[Dữ liệu PHP](https://raw.githubusercontent.com/bebebus/SDK/main/php/appendix/catalog.php)　[Dữ liệu Python](https://raw.githubusercontent.com/bebebus/SDK/main/python/appendix/catalog.py)　[Dữ liệu Go](https://raw.githubusercontent.com/bebebus/SDK/main/go/appendix/catalog.go)　[Dữ liệu Java](https://raw.githubusercontent.com/bebebus/SDK/main/java/appendix/catalog.json)

| Mã ISO | Quốc gia | Loại tiền thông dụng |
|---|---|---|
| `PH` | Philippines | `PHP` |
| `ID` | Indonesia | `IDR` |
| `IN` | Ấn Độ | `INR` |
| `BD` | Bangladesh | `BDT` |
| `VN` | Việt Nam | `VND` |
| `TH` | Thái Lan | `THB` |
| `MM` | Myanmar | `MMK` |
| `MY` | Malaysia | `MYR` |
| `BR` | Brazil | `BRL` |
| `MX` | Mexico | `MXN` |
| `CN` | Trung Quốc | `CNY` |

## Phụ lục: Loại tiền

Bảng này là ảnh chụp tĩnh; nguồn có thẩm quyền lúc chạy là `countries/query` / `pay-methods/query`. Trường `currency` dùng **ISO 4217** (`USDT` là ngoại lệ crypto của nền tảng). Số tiền là các số nguyên bằng giá trị thực × 10000. Việc một loại tiền hiện có dùng được hay không do truy vấn nhóm / phương thức thanh toán quyết định.

Tải dữ liệu：[Dữ liệu Node.js](https://raw.githubusercontent.com/bebebus/SDK/main/nodejs/appendix/catalog.json)　[Dữ liệu PHP](https://raw.githubusercontent.com/bebebus/SDK/main/php/appendix/catalog.php)　[Dữ liệu Python](https://raw.githubusercontent.com/bebebus/SDK/main/python/appendix/catalog.py)　[Dữ liệu Go](https://raw.githubusercontent.com/bebebus/SDK/main/go/appendix/catalog.go)　[Dữ liệu Java](https://raw.githubusercontent.com/bebebus/SDK/main/java/appendix/catalog.json)

| Loại tiền | Tên | Loại |
|---|---|---|
| `PHP` | Peso Philippines | Tiền pháp định |
| `IDR` | Rupiah Indonesia | Tiền pháp định |
| `INR` | Rupee Ấn Độ | Tiền pháp định |
| `BDT` | Taka Bangladesh | Tiền pháp định |
| `VND` | Đồng Việt Nam | Tiền pháp định |
| `THB` | Baht Thái Lan | Tiền pháp định |
| `MMK` | Kyat Myanmar | Tiền pháp định |
| `MYR` | Ringgit Malaysia | Tiền pháp định |
| `BRL` | Real Brazil | Tiền pháp định |
| `MXN` | Peso Mexico | Tiền pháp định |
| `CNY` | Nhân dân tệ | Tiền pháp định |
| `USDT` | Tether | Tiền mã hóa |

## Phiên bản và tương thích

- Phiên bản chính nằm trong đường dẫn: `<BASE_URL>/v1/...`. Cả thu hộ và chi hộ đều chỉ ở v1.
- v1 chỉ phát triển theo cách **tương thích ngược** (thêm trường tùy chọn, sửa lỗi); sẽ không thay đổi ý nghĩa của các trường hiện có hoặc siết chặt kiểm tra hiện có.
- Nếu cần thay đổi phá vỡ tương thích, một đường dẫn `/v2` mới sẽ được thêm trong khi `/v1` được giữ lại một thời gian, để merchant có thể chuyển đổi một cách thuận lợi.
- Gọi `GET <BASE_URL>/v1/version` để truy vấn phiên bản máy chủ hiện tại (không yêu cầu xác thực).

### Ghi chú hành vi

- `status` đối ngoại của thu hộ không còn trả về `expired`: hết thời gian chưa thanh toán được chuẩn hóa thành `pending` (đang xử lý); hãy dựa vào kết quả truy vấn đơn.
- Thu hộ chỉ gửi callback cho merchant khi đơn thành công (`success`); các trạng thái không thành công như failed/hết thời gian chờ không kích hoạt callback, và merchant nên lấy kết quả cuối qua API truy vấn đơn (chi hộ không chịu hạn chế này).

=== Tài liệu API kết thúc ===

Bây giờ hãy bắt đầu. Nếu trường hoặc luồng còn chưa rõ, hãy hỏi tôi trước khi viết mã.
