# API công khai TinoPay

API công khai của TinoPay chạy tại `https://pay.tino.vn`: truy vấn lịch sử giao dịch, lấy danh sách ngân hàng và sinh ảnh mã VietQR. API này khác [API merchant](/docs/tinopay-start#goi-api-merchant) (`https://api.tino.vn/service/<id>/tinopay/…`, token đăng nhập Tino) dùng để quản lý merchant, tài khoản ngân hàng, webhook.

Cập nhật: 2026-10-25. Nguồn: [tài liệu TinoPay](https://pay.tino.vn/docs).

## Hai cách tích hợp

| Hướng | Mô tả | Dùng khi |
| --- | --- | --- |
| API | bạn gọi tới TinoPay để truy vấn dữ liệu (lịch sử giao dịch, danh sách ngân hàng) và sinh mã QR | kiểm tra, đối soát, hiện QR thanh toán |
| Webhook | TinoPay gửi dữ liệu giao dịch tới URL của bạn ngay khi phát sinh | xác nhận thanh toán tức thời, không cần hỏi lặp |

Nên dùng [webhook](/docs/tinopay-webhook) để nhận giao dịch tức thời và dùng [lịch sử giao dịch](#lich-su-giao-dich) để đối soát hoặc dự phòng.

## Thông tin cơ bản

| Mục | Giá trị |
| --- | --- |
| Địa chỉ gốc | `https://pay.tino.vn` |
| Tiền tố | endpoint API nằm dưới `/api`, ví dụ `https://pay.tino.vn/api/transactions`; riêng ảnh QR ở `https://pay.tino.vn/img-qr` |
| Định dạng | JSON, UTF-8 |
| Tiền tệ | VND, số nguyên, không có phần thập phân |
| Xác thực | `Authorization: Bearer <api_token>`, trừ danh sách ngân hàng và ảnh QR |

```bash
curl -G "https://pay.tino.vn/api/transactions" \
  -H "Authorization: Bearer $TINOPAY_API_TOKEN" \
  -H "Accept: application/json"
```

## Xác thực

Mọi request cần xác thực gửi API token ở header `Authorization` theo chuẩn Bearer:

```http
Authorization: Bearer <api_token>
```

### Tạo API token

- Trên web TinoPay: menu Tích hợp → API Access (`/api-access`), bấm Tạo API Token, đặt tên gợi nhớ (ví dụ `server-production`) rồi sao chép token ngay.
- Bằng API merchant: [Tạo API token](/docs/api/tinopay-post-api-tokens), xem mục [API token](/docs/tinopay-start#api-token) của trang Bắt đầu.

Token gốc chỉ hiện **một lần** khi tạo. Làm mất thì thu hồi token cũ và tạo token mới. Tạo được nhiều token và thu hồi bất kỳ token nào ở trang API Access.

### Phạm vi truy cập

- Mỗi token gắn với một tài khoản chủ sở hữu và chỉ đọc được dữ liệu (giao dịch, tài khoản ngân hàng) của chủ token đó.
- Tham số trỏ tới tài nguyên không thuộc về bạn (ví dụ `bank_account_id` của người khác) thì API không trả dữ liệu.

### Ví dụ gọi

JavaScript:

```javascript
const res = await fetch("https://pay.tino.vn/api/transactions", {
  headers: { Authorization: `Bearer ${process.env.TINOPAY_API_TOKEN}`, Accept: "application/json" },
});
const data = await res.json();
```

PHP (Laravel):

```php
$response = Http::withToken(env('TINOPAY_API_TOKEN'))
    ->acceptJson()
    ->get('https://pay.tino.vn/api/transactions');
$data = $response->json();
```

### Lỗi xác thực

| Tình huống | HTTP | Body |
| --- | --- | --- |
| thiếu hoặc sai định dạng header `Authorization` | 401 | `{"message": "Thiếu API Token. Vui lòng gửi header Authorization: Bearer {token}."}` |
| token không tồn tại hoặc đã bị thu hồi | 401 | `{"message": "API Token không hợp lệ."}` |

### Bảo mật

- TinoPay lưu token dưới dạng băm SHA-256, không lưu token gốc: token gốc chỉ xem được một lần khi tạo.
- Mỗi request hợp lệ cập nhật thời điểm dùng gần nhất (`last_used_at`) của token, giúp theo dõi token nào đang hoạt động.
- Không đưa token vào mã phía client (trình duyệt, app di động) hay commit lên Git: chỉ dùng ở máy chủ.
- Token bị lộ thì thu hồi ngay ở trang API Access; token đã thu hồi trả 401 từ request kế tiếp.

## Lịch sử giao dịch

```http
GET https://pay.tino.vn/api/transactions
```

- Xác thực: bắt buộc.
- Trả giao dịch của mọi tài khoản ngân hàng thuộc chủ token.
- Mặc định lấy giao dịch trong ngày hiện tại; khoảng khác thì truyền `transaction_date_from`, `transaction_date_to`.
- Sắp xếp theo `transaction_date` giảm dần (mới nhất trước), có phân trang.

Tham số truy vấn, đều tuỳ chọn:

| Tham số | Kiểu | Mặc định | Mô tả |
| --- | --- | --- | --- |
| `q` | string | — | tìm (LIKE) theo `reference_number`, `transaction_content`, `transaction_id` |
| `bank_account_id` | string (UUID) | — | lọc theo một tài khoản ngân hàng; UUID phải thuộc chủ token |
| `type` | string | — | `in` tiền vào, `out` tiền ra |
| `transaction_date_from` | date (`Y-m-d`) | hôm nay | ngày bắt đầu (tính cả ngày này) |
| `transaction_date_to` | date (`Y-m-d`) | hôm nay | ngày kết thúc (tính cả ngày này); tối đa 90 ngày kể từ `from` |
| `per_page` | int (1–100) | `20` | số bản ghi mỗi trang |
| `page` | int | `1` | trang hiện tại |

Khoảng thời gian:

- Không truyền cả `from` lẫn `to`: lấy trong ngày hôm nay.
- Chỉ truyền một trong hai: lấy trọn đúng ngày đó.
- `from` phải nhỏ hơn hoặc bằng `to`, cách nhau tối đa 90 ngày.

```bash
curl -G "https://pay.tino.vn/api/transactions" \
  -H "Authorization: Bearer $TINOPAY_API_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "q=DH12345" \
  --data-urlencode "type=in" \
  --data-urlencode "transaction_date_from=2026-10-01" \
  --data-urlencode "transaction_date_to=2026-10-20" \
  --data-urlencode "per_page=20"
```

```javascript
const params = new URLSearchParams({ q: "DH12345", type: "in", transaction_date_from: "2026-10-01", transaction_date_to: "2026-10-20", per_page: "20" });
const res = await fetch(`https://pay.tino.vn/api/transactions?${params}`, {
  headers: { Authorization: `Bearer ${process.env.TINOPAY_API_TOKEN}`, Accept: "application/json" },
});
const json = await res.json();
```

Response `200`:

```json
{
  "data": [
    {
      "id": 1032,
      "reference_number": "0551dTCO-82LiomOss",
      "transaction_content": "DH12345 chuyen tien mua hang",
      "transaction_id": "20261020083338000006",
      "bank_account_id": "9c1e6f2a-5b7d-4e1a-9f3c-2d8b6a4e0c11",
      "virtual_number": "9624QA01",
      "amount": 150000,
      "type": 1,
      "type_label": "in",
      "status": 0,
      "transaction_date": "2026-10-20 08:33:38",
      "created_at": "2026-10-20 08:33:40"
    }
  ],
  "links": {
    "first": "https://pay.tino.vn/api/transactions?page=1",
    "last": "https://pay.tino.vn/api/transactions?page=3",
    "prev": null,
    "next": "https://pay.tino.vn/api/transactions?page=2"
  },
  "meta": { "current_page": 1, "from": 1, "last_page": 3, "per_page": 20, "to": 20, "total": 57 }
}
```

| Trường trong `data[]` | Kiểu | Mô tả |
| --- | --- | --- |
| `id` | int | ID giao dịch |
| `reference_number` | string \| null | mã tham chiếu của ngân hàng (core reference) |
| `transaction_content` | string | nội dung, diễn giải giao dịch |
| `transaction_id` | string | mã giao dịch |
| `bank_account_id` | string (UUID) | tài khoản ngân hàng phát sinh giao dịch |
| `virtual_number` | string \| null | số tài khoản ảo, nếu có |
| `amount` | int | số tiền (VND) |
| `type` | int | `1` tiền vào, `2` tiền ra |
| `type_label` | string | `in` tiền vào, `out` tiền ra |
| `status` | int | trạng thái giao dịch |
| `transaction_date` | string (`Y-m-d H:i:s`) | thời điểm giao dịch thực tế |
| `created_at` | string (`Y-m-d H:i:s`) | thời điểm TinoPay ghi nhận |

`links` và `meta` là cấu trúc phân trang của Laravel API Resource: duyệt trang theo `meta.last_page` hoặc `links.next`.

| Lỗi | HTTP | Body |
| --- | --- | --- |
| `from` lớn hơn `to` | 422 | `{"message": "transaction_date_from phải nhỏ hơn hoặc bằng transaction_date_to."}` |
| khoảng quá 90 ngày | 422 | `{"message": "Khoảng thời gian tối đa là 90 ngày."}` |
| sai kiểu dữ liệu tham số | 422 | lỗi validate của Laravel: `{"message": …, "errors": {…}}` |

## Danh sách ngân hàng

```http
GET https://pay.tino.vn/api/banks
```

Ngân hàng TinoPay hỗ trợ, kèm mã ngân hàng (`code`) và mã BIN: dùng cho ô chọn ngân hàng hoặc lấy `code` để [tạo mã VietQR](#tao-ma-vietqr). Không cần xác thực. Trả một mảng JSON, không bọc trong `data`.

```bash
curl "https://pay.tino.vn/api/banks" -H "Accept: application/json"
```

```json
[
  { "id": 1, "code": "BIDV", "bin": "970418", "name": "Ngân hàng TMCP Đầu tư và Phát triển Việt Nam", "created_at": "2025-01-01 00:00:00", "updated_at": "2025-01-01 00:00:00" },
  { "id": 2, "code": "MB", "bin": "970422", "name": "Ngân hàng TMCP Quân đội", "created_at": "2025-01-01 00:00:00", "updated_at": "2025-01-01 00:00:00" }
]
```

| Trường | Kiểu | Mô tả |
| --- | --- | --- |
| `id` | int | ID ngân hàng trong TinoPay |
| `code` | string | mã viết tắt (ví dụ `BIDV`, `MB`), dùng cho tham số `bank_code` khi tạo mã QR |
| `bin` | string | mã định danh ngân hàng (BIN) theo chuẩn Napas, VietQR |
| `name` | string | tên đầy đủ của ngân hàng |

Danh sách theo đúng dữ liệu ngân hàng đang cấu hình trong TinoPay: dùng nguyên giá trị `code` từ đây khi gọi API khác.

## Tạo mã VietQR

```http
GET https://pay.tino.vn/img-qr
```

Sinh mã VietQR động để khách quét và chuyển khoản. Endpoint trả thẳng ảnh PNG nên nhúng được vào thẻ `<img>`, không cần tự vẽ QR.

- Không cần xác thực. Không có tiền tố `/api`.
- Trả ảnh `image/png`, có header `Access-Control-Allow-Origin: *` nên dùng được từ trình duyệt.
- Mã theo chuẩn VietQR, Napas 247 (dịch vụ chuyển nhanh QRIBFTTA).

| Tham số | Bắt buộc | Mô tả |
| --- | --- | --- |
| `bank_code` | có | mã ngân hàng thụ hưởng (ví dụ `BIDV`, `MB`), lấy từ [danh sách ngân hàng](#danh-sach-ngan-hang) |
| `account_no` | có | số tài khoản thụ hưởng |
| `account_name` | không | tên chủ tài khoản, hiện trên khung ảnh |
| `amount` | không | số tiền (VND); chỉ áp dụng khi trong khoảng 1.000 – 9.999.999.999.999 |
| `memo` | không | nội dung chuyển khoản, tối đa 25 ký tự (dài hơn thì bị bỏ qua) |
| `template` | không | `default` ảnh có khung (mặc định), `only-qr` chỉ mã QR |
| `is_mask` | không | `1`: che bớt số tài khoản trên ảnh có khung |

```html
<img src="https://pay.tino.vn/img-qr?bank_code=BIDV&account_no=96240000001&amount=150000&memo=DH12345" alt="Quét QR để thanh toán" width="320">
```

Đủ tham số:

```text
https://pay.tino.vn/img-qr?bank_code=BIDV&account_no=96240000001&account_name=CONG%20TY%20ABC&amount=150000&memo=DH12345&template=default&is_mask=1
```

Chỉ mã QR, không khung:

```text
https://pay.tino.vn/img-qr?bank_code=BIDV&account_no=96240000001&amount=150000&memo=DH12345&template=only-qr
```

Thiếu hoặc sai tham số bắt buộc thì endpoint trả văn bản, không phải ảnh:

| Tình huống | Trả về |
| --- | --- |
| `bank_code` không hợp lệ hoặc không có | `Vui lòng nhập đúng mã ngân hàng` |
| thiếu `account_no` | `Vui lòng nhập số tài khoản` |

Trong luồng thanh toán, đặt `memo` là mã đơn hàng duy nhất (ví dụ `DH12345`). Khi khách chuyển khoản, [webhook](/docs/tinopay-webhook) nhận giao dịch kèm nội dung này (và `code` nếu khớp [mã thanh toán](/docs/api/tinopay-patch-merchant)), nhờ đó khớp đúng đơn hàng và xác nhận thanh toán tự động.

## Hỗ trợ

Vướng mắc khi tích hợp: tạo ticket hỗ trợ ngay trong trang quản trị TinoPay.
