<!-- pay2s-offline-docs-v1 -->
# Tài liệu Pay2S đính kèm

Ngày chụp nội dung tài liệu: 2026-09-29 (UTC).
Nguồn: https://docs.pay2s.vn/

Đây là bản tài liệu lưu tại ngày trên, không tự cập nhật sau khi tải. Nội dung các trang được chép đầy đủ bên dưới; không cần mở link để đọc. Các liên kết khác là nguồn tham khảo bổ sung. Nếu thiếu thông tin cần thiết, hãy hỏi người dùng thay vì tự đoán.

## Mục lục
- 🔐 Authentication / Xác thực đối tác
- 🎯 Hủy OneQR - Hủy QR động
- 🎯 OneQR - Khái niệm
- 🎯 Khởi tạo OneQR - Tạo QR động
- 🔍 Kiểm tra OneQR - Xem chi tiết QR
- 📋 Danh sách OneQR - Xem QR đã tạo

---

Nguồn trang: https://docs.pay2s.vn/oneqr/authentication.html

# 🔐 Authentication / Xác thực đối tác

## 🧭 Giới thiệu

Hệ thống **Pay2S OneQR** sử dụng cơ chế xác thực hai tầng để đảm bảo an toàn cho các kết nối từ phía người dùng.

1. **Bước 1:** Đối tác đăng nhập vào [Pay2S.vn](https://pay2s.vn) để lấy **Access Key** và **Secret Key**.  
2. **Bước 2:** Sử dụng cặp khóa này để thực hiện **Basic Authentication**, lấy về **Bearer Token** tạm thời.  
3. **Bước 3:** Dùng **Bearer Token** này để gọi các API như `/api/vcb/v1/qr/create`, `api/vcb/v1/qr/cancel`, v.v.

---

## 🧩 Luồng xác thực tổng quan

```mermaid
sequenceDiagram
    participant C as Client (Partner)
    participant G as Pay2S Partner Gateway
    participant A as Auth Service
    participant API as Protected API

    C->>G: POST /api/auth/token (Basic Auth)
    G->>A: Xác minh Access Key / Secret Key
    A-->>G: Trả về Bearer Token (hiệu lực 3600s)
    G-->>C: 200 OK + token
    C->>API: Authorization: Bearer {token}
    API-->>C: Trả về dữ liệu (banks, webhooks,...)
```

---

## 🧾 Thông tin chi tiết

### **Endpoint**
`POST http://api.pay2s.vn/api/auth/token`

### **Headers**
| Tên header | Giá trị mẫu | Mô tả |
|-------------|-------------|-------|
| `Authorization` | `Basic base64(access_key:secret_key)` | Dạng xác thực cơ bản |
| `Content-Type` | `application/json` | Bắt buộc |

### **Ví dụ Base64**
Giả sử:
```
access_key = e222c2c10059cca0e27bf43d0438bba37
secret_key = 83f84c5a1d94ad4a1d3810292ad55a0d
```
→ Ghép chuỗi:  
`e222c2c10059cca0e27bf43d0438bba372:83f84c5a1d94ad4a1d3810292ad55a0d`  
→ Mã hoá Base64 thành:  
`ZT222c2c10059cca0e27bf43d0438bba372OjgzZjg0YzVhMWQ5NGFkNGExZDM4MTAyOTJhZDU1YTBk`

→ Header:
```
Authorization: Basic ZT222c2c10059cca0e27bf43d0438bba372OjgzZjg0YzVhMWQ5NGFkNGExZDM4MTAyOTJhZDU1YTBk
```

---

## 🧠 Phản hồi mẫu

### ✅ Thành công
```json
{
  "success": true,
  "data": {
    "token_type": "Bearer",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....",
    "expires_in": 3600
  }
}
```

### ❌ Lỗi xác thực
```json
{
    "success": false,
    "message": "Authentication failed",
    "data": null,
    "error": "Request failed with status code 401"
}
```

---

## 🧩 Gọi API bằng Token

Sau khi nhận được **Bearer Token**, bạn có thể gọi các endpoint bảo mật khác như sau:

**Ví dụ:**
```
POST http://api.pay2s.vn/api/vcb/v1/qr/cancel HTTP/1.1
Host: api.pay2s.vn
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

**Kết quả:**
```json
{
    "success": true,
    "message": "QR transaction cancelled successfully",
    "data": {
        "transactionId": "0e907bd2-74d0-4c49-96ba-e64660736a2a",
        "orderId": "123456",
        "status": "cancelled",
        "cancelResponse": {
            "code": "00",
            "message": "Succeed",
            "data": {
                "subCode": "00",
                "subMessage": "Succeed",
                "requestId": "0368e7f9-902c-4bd3-adb6-c5766e328856",
                "checksum": "3E808EBC44F716562EE1290FF9C2A5F0",
                "vcbResponse": {
                    "code": "00",
                    "message": "Succeed",
                    "requestId": "0368e7f9-902c-4bd3-adb6-c5766e328856",
                    "subCode": "00",
                    "subMessage": "Succeed",
                    "serverTime": "15:46:33 08/06/2026",
                    "nodeOut": "10.1.60.186",
                    "nodeIn": "",
                    "operation": "CancelInvoice",
                    "success": true,
                    "idQrCode": "31928425",
                    "checksum": "25B3AFCB0ABA8EF2DADE66255156653C"
                },
                "success": true
            }
        }
    }
}
```

---

::: tip 🔑 Gợi ý
- Token có hiệu lực **3600 giây (1 giờ)**. Sau khi hết hạn, cần gọi lại `/api/auth/token` để lấy token mới.
- Hỗ trợ xác thực **Basic Auth → Bearer Token** (không cần tạo chữ ký HMAC thủ công).
:::


---

Nguồn trang: https://docs.pay2s.vn/oneqr/huy-oneqr.html

# 🎯 Hủy OneQR - Hủy QR động

## 📱 Tổng quan

Để hủy một mã QR động đã tạo, bạn gọi API **Cancel QR Code** của OneQR. Khi hủy QR, giao dịch sẽ không thể thanh toán được nữa.

---

## 🔌 Endpoint

```
POST http://api.pay2s.vn/api/{bank_code}/v1/qr/cancel
```

| Tham số | Ví dụ | Mô tả |
|--------|-------|-------|
| `{bank_code}` | `vcb` | Mã ngân hàng (vcb = Vietcombank, acb = ACB Bank, ...) |

---

## 📤 Request

### Headers

```json
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
```

> **Lưu ý**: `<token>` là Bearer token được cấp từ Pay2S

### Body Parameters (JSON)

| Tham số | Loại | Bắt buộc | Mô tả |
|--------|------|---------|-------|
| `orderId` | String | ✓ | ID đơn hàng trong hệ thống của bạn |
| `traceNumber` | String | ✓ | Mã định danh QR được trả về từ API tạo QR |

### ✅ Ví dụ Request (cURL)

```bash
curl --location 'http://api.pay2s.vn/api/vcb/v1/qr/cancel' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_BEARER_TOKEN' \
  --data '{
    "orderId": "123456",
    "traceNumber": "QRPP2S26159194HHKZE"
  }'
```

---

## 📥 Response

### ✅ Thành công (200 OK)

```json
{
    "success": true,
    "message": "QR transaction cancelled successfully",
    "data": {
        "transactionId": "0e907bd2-74d0-4c49-96ba-e64660736a2a",
        "orderId": "123456",
        "status": "cancelled",
        "cancelResponse": {
            "code": "00",
            "message": "Succeed",
            "data": {
                "subCode": "00",
                "subMessage": "Succeed",
                "requestId": "0368e7f9-902c-4bd3-adb6-c5766e328856",
                "checksum": "3E808EBC44F716562EE1290FF9C2A5F0",
                "vcbResponse": {
                    "code": "00",
                    "message": "Succeed",
                    "requestId": "0368e7f9-902c-4bd3-adb6-c5766e328856",
                    "subCode": "00",
                    "subMessage": "Succeed",
                    "serverTime": "15:46:33 08/06/2026",
                    "nodeOut": "10.1.60.186",
                    "nodeIn": "",
                    "operation": "CancelInvoice",
                    "success": true,
                    "idQrCode": "31928425",
                    "checksum": "25B3AFCB0ABA8EF2DADE66255156653C"
                },
                "success": true
            }
        }
    }
}
```

| Trường | Mô tả |
|--------|-------|
| `success` | Trạng thái: `true` = Hủy thành công |
| `message` | Mô tả: `QR transaction cancelled successfully` |
| `status` | Trạng thái: `cancelled` = QR đã bị hủy |
| `transactionId` | Mã giao dịch duy nhất |
| `orderId` | ID đơn hàng được hủy |


---

## 💻 Code mẫu - Hủy QR

::: code-group

```php [PHP]
<?php
$bearerToken = "YOUR_BEARER_TOKEN";
$url = "http://api.pay2s.vn/api/vcb/v1/qr/cancel";

$data = [
    'orderId' => "123456",
    'traceNumber' => "QRPP2S26159194HHKZE"
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . $bearerToken,
    "Content-Type: application/json"
]);

$response = curl_exec($ch);
$result = json_decode($response, true);

if ($result['success'] === true) {
    $orderId = $result['data']['orderId'];
    $status = $result['data']['status'];
    echo "Order " . $orderId . " cancelled with status: " . $status;
} else {
    echo "Error: " . $result['message'];
}

curl_close($ch);
?>
```

```javascript [Node.js]
const axios = require('axios');

const bearerToken = "YOUR_BEARER_TOKEN";

const payload = {
  orderId: "123456",
  traceNumber: "QRPP2S26159194HHKZE"
};

axios.post("http://api.pay2s.vn/api/vcb/v1/qr/cancel", payload, {
  headers: {
    "Authorization": `Bearer ${bearerToken}`,
    "Content-Type": "application/json"
  }
})
  .then(response => {
    if (response.data.success === true) {
      console.log("Order cancelled:", response.data.data.orderId);
      console.log("Status:", response.data.data.status);
    } else {
      console.error("Error:", response.data.message);
    }
  })
  .catch(err => console.error(err));
```

```python [Python]
import requests
import json

bearer_token = "YOUR_BEARER_TOKEN"

payload = {
    'orderId': "123456",
    'traceNumber': "QRPP2S26159194HHKZE"
}

headers = {
    "Authorization": f"Bearer {bearer_token}",
    "Content-Type": "application/json"
}

response = requests.post("http://api.pay2s.vn/api/vcb/v1/qr/cancel", 
                         data=json.dumps(payload), 
                         headers=headers)
result = response.json()

if result['success'] == True:
    print("Order cancelled:", result['data']['orderId'])
    print("Status:", result['data']['status'])
else:
    print("Error:", result['message'])
```

:::

---

## ⚡ Best Practices

1. **Validate parameters**: Kiểm tra `orderId` và `traceNumber` không rỗng
2. **Error handling**: Kiểm tra `success` trước xử lý `data`
3. **Timing**: Chỉ hủy QR khi khách hàng từ chối thanh toán
4. **Status check**: Không hủy QR đã được thanh toán
5. **Retry logic**: Nếu request fail, retry sau 2-3 giây
6. **Update database**: Cập nhật status = 'cancelled' trong database
7. **Trace number**: Lưu trữ traceNumber để audit log
8. **User notification**: Thông báo cho khách hàng khi QR bị hủy
9. **Timeout handling**: Đặt timeout hợp lý (30-60 giây)
10. **Logging**: Log toàn bộ cancel request/response

---

## 📌 Luồng hủy QR

```
1. Khách từ chối thanh toán → 2. Hệ thống gọi API hủy QR → 
3. Gửi orderId + traceNumber → 4. Pay2S xác nhận hủy → 
5. QR không thể scan nữa → 6. Cập nhật status = cancelled → 
7. Thông báo khách hàng
```

---

> **Tiếp theo**: Xem [Tạo OneQR](/oneqr/khoi-tao-oneqr) để tạo QR mới hoặc [Webhook](/webhook/tai-lieu-ky-thuat.html) để nhận thông báo thanh toán


---

Nguồn trang: https://docs.pay2s.vn/oneqr/khai-niem.html

# 🎯 OneQR - Khái niệm

## 📱 OneQR là gì?

**OneQR** là giải pháp **QR Code thanh toán thống nhất** được phát triển bởi Pay2S. Nó cho phép doanh nghiệp, cửa hàng tạo một mã QR duy nhất để nhận thanh toán từ **tất cả ngân hàng, ví điện tử, và nền tảng thanh toán** mà không cần tạo QR riêng cho từng cổng.

### 🎁 Lợi ích chính

- **Không cần nhập tiền**: Khách scan → Số tiền đã cố định → Xác nhận → Xong
- **Tự động khoá số tiền**: Không thể thanh toán sai số tiền
- **Tức thì xác nhận**: Tiền được ghi nhận ngay lập tức (0-1 giây)
- **Bảo mật cao**: 1 QR = 1 giao dịch, không tái sử dụng
- **Dễ tracking**: Mỗi QR liên kết 1-to-1 với 1 invoice/order
- **Không phụ thuộc ngân hàng**: Tất cả NH, ví điều support
- **Webhook tự động**: Cập nhật hệ thống merchant ngay khi thanh toán

---

## 🔄 Cách hoạt động

```mermaid
graph LR
    A["👤 Khách hàng<br/>Scan QR"] -->|Chọn ngân hàng| B["🏦 Ngân hàng<br/>Vietcombank, ACB, Momo..."]
    B -->|Chuyển tiền| C["💳 OneQR Gateway<br/>Pay2S"]
    C -->|Xác nhận| D["✅ Tài khoản<br/>Cập nhật"]
    D -->|📊 Webhook| E["📈 Dashboard<br/>Xem lịch sử"]
```

### Các bước chi tiết:

1. **Khách hàng quét QR** → App ngân hàng hoặc ví điện tử mở
2. **Chọn ngân hàng** → Vietcombank, Techcombank, Momo, ZaloPay, v.v.
3. **Nhập số tiền & nội dung** → Tùy chọn (nếu QR động)
4. **Xác nhận thanh toán** → Gửi đến Pay2S
5. **Cập nhật tài khoản** → Tiền được ghi nhận ngay
6. **Webhook thông báo** → Hệ thống merchant nhận sự kiện

---

## ✨ Dynamic QR - Nền tảng OneQR

**OneQR hoàn toàn là một hệ thống QR động (Dynamic QR)**, không phải QR tĩnh. Điều này có nghĩa:

- 🔄 **Mã QR thay đổi** theo từng giao dịch
- 💰 **Số tiền cố định** sẵn trong QR
- 🎯 **1-to-1 mapping**: 1 QR = 1 giao dịch
- 📱 **Scan → Thanh toán**: Khách không cần nhập tiền

### 📌 Ví dụ cực kỳ đơn giản:

```
Merchant tạo đơn hàng: 500,000 đ
  ↓
OneQR sinh 1 QR code với số tiền 500k (đã cố định)
  ↓
Khách scan QR
  ↓
App ngân hàng hiển thị: "Thanh toán 500,000 đ cho [Shop]"
  ↓
Khách xác nhận 1 lần → Xong
```

**Lợi ích của Dynamic QR:**
- ✅ Không cần nhập tiền (giảm lỗi)
- ✅ Tự động xác nhận (lập tức)
- ✅ Từ chối tiền sai số (bảo mật)
- ✅ Perfect cho E-commerce, invoice, checkout online

---

## 🎯 Use Cases của OneQR (Dynamic QR)

### 🏪 E-commerce / Web Store
- Sinh QR **động cho từng đơn hàng**
- Gửi QR trong email xác nhận hoặc checkout page
- Khách scan → Thẳng thanh toán (không cần nhập tiền)
- Tự động cập nhật khi thanh toán

### 💳 Hóa đơn điện tử
- 1 QR per invoice với số tiền đã cố định
- Email/SMS gửi QR tới khách
- Khách scan → Xác nhận → Xong (30 giây)

### 🎓 Giáo dục
- QR cho từng khoản học phí
- QR thanh toán bồi dưỡng, khóa học online
- Sinh viên scan → Trả tiền ngay

### 🏥 Y tế
- QR thanh toán viện phí cho từng bệnh nhân
- QR cho các dịch vụ khám ngoài giờ
- Bệnh nhân scan tại quầy tiếp đón

### 🎪 Sự kiện / Tổ chức
- QR vé cưới, sinh nhật, hội họp
- QR đăng ký workshop với phí
- QR quyên góp có số tiền cố định

### 🚚 Logistics / Vận chuyển
- QR thanh toán COD (tài xế scan)
- QR phí vận chuyển trước khi ship
- Tự động khớp với đơn hàng

---

## 📊 So sánh QR vs Phương pháp khác

| Tiêu chí | OneQR | Transfer thủ công | POS | BNPL |
|---------|-------|-------------------|-----|------|
| **Chi phí** | Thấp | Miễn phí | Cao ($1-3k) | Cao |
| **Tốc độ** | Tức thì | Chậm | Nhanh | Chậm |
| **Setup** | 2 phút | 0 phút | 1 ngày | 1 tuần |
| **Hỗ trợ ngân hàng** | 50+ | Chỉ 1 | 1 | Giới hạn |
| **Kỳ vọng** | Cao | Cao | Trung bình | Thấp |

---

## 🔐 Bảo mật OneQR

### ✅ An toàn như thế nào?

1. **Mã hóa SSL/TLS**: Tất cả giao dịch được mã hóa
2. **HMAC Signature**: Xác thực nguồn gốc dữ liệu
3. **Token Authentication**: Mỗi QR có token riêng
4. **Rate Limiting**: Ngăn chặn spam/fraud
5. **Webhook Verification**: Xác nhận callback từ Pay2S

### ⚠️ Những lưu ý

- **Không lưu số tiền** trên QR tĩnh (người có thể thay đổi)
- **QR động phải bảo mật** (private key phải giữ kín)
- **Kiểm tra webhook** trước cập nhật database
- **Monitor fraud patterns** để phát hiện lạm dụng

---

## 💡 Ưu và nhược điểm

### ✅ Ưu điểm
- Cực kỳ đơn giản (1 QR thay vì 10 QR)
- Hỗ trợ tất cả ngân hàng, ví
- Chi phí thấp
- Không cần thiết bị phức tạp
- Tốc độ giao dịch nhanh

### ⚠️ Nhược điểm
- Cần kết nối internet để cập nhật
- Không thích hợp cho offline (cửa hàng không internet)
- QR tĩnh dễ bị lạm dụng nếu không kiểm tra
- Cần webhook để thông báo

---

## 🚀 Xu hướng tương lai

### Các tính năng sắp tới:
- **AI Detection**: Phát hiện giao dịch đáo hạn
- **Dynamic Pricing**: Giá tự động thay đổi theo thời gian
- **Multi-currency**: Thanh toán cross-border bằng QR
- **Subscription**: Tính năng đăng ký tự động
- **Split Payment**: Chia tiền 1 QR cho nhiều người

---

## 📞 Hỗ trợ

| Nội dung | Liên hệ |
|---------|---------|
| **Kỹ thuật** | support@pay2s.vn |
| **Kinh doanh** | sales@pay2s.vn |
| **Cấp cứu** | +84-XXX-XXX-XXX |

---

> 💬 **Tiếp theo**: Xem [Tạo OneQR](/oneqr/khoi-tao-oneqr) để bắt đầu tạo mã QR của bạn!


---

Nguồn trang: https://docs.pay2s.vn/oneqr/khoi-tao-oneqr.html

# 🎯 Khởi tạo OneQR - Tạo QR động

## 📱 Tổng quan

Để tạo một mã QR động cho giao dịch, bạn gọi API **Create QR Code** của OneQR. Mỗi lần gọi sẽ sinh ra **1 QR mới** với các thông tin giao dịch cụ thể.

---

## 🔌 Endpoint

```
POST http://api.pay2s.vn/api/{bank_code}/v1/qr/create
```

| Tham số | Ví dụ | Mô tả |
|--------|-------|-------|
| `{bank_code}` | `vcb` | Mã ngân hàng (vcb = Vietcombank, acb = ACB Bank, ...) |

---

## 📤 Request

### Headers

```json
{
  "Content-Type": "application/json",
  "Authorization": "Bearer <token>"
}
```

> **Lưu ý**: `<token>` là Bearer token được cấp từ Pay2S

### Body Parameters (JSON)

| Tham số | Loại | Bắt buộc | Mô tả |
|--------|------|---------|-------|
| `orderId` | String | ✓ | ID đơn hàng trong hệ thống của bạn |
| `amount` | Number | ✓ | Số tiền (VND) - ví dụ: 18000, 500000 |
| `merchantName` | String | ✓ | Tên cửa hàng/thương nhân |
| `merchantId` | String | ✓ | Mã thương nhân do ngân hàng cấp |
| `terminalId` | String | ✓ | ID thiết bị/terminal |
| `description` | String | ✓ | Nội dung thanh toán |
| `template` | String | - | Kiểu template hiển thị (ví dụ: `frame`) |
| `maskAccount` | Boolean | - | Ẩn số tài khoản (default: false) |
| `frameId` | Number | - | ID khung hình để hiển thị QR |

### ✅ Ví dụ Request (cURL)

```bash
curl --location 'http://api.pay2s.vn/api/vcb/v1/qr/create' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer YOUR_BEARER_TOKEN' \
  --data '{
    "orderId": "123456",
    "amount": 10000,
    "merchantName": "CONG TY CO PHAN FUTE",
    "merchantId": "091000000900000",
    "terminalId": "60000000",
    "description": "THANH TOAN DON HANG",
    "template": "frame",
    "maskAccount": true,
    "frameId": 1
  }'
```

---

## 📥 Response

### ✅ Thành công (200 OK)

```json
{
    "code": "00",
    "desc": "Succeed",
    "data": {
        "qrDataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA1IAAARMCAYAAACTXaJiAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR4nO...",
        "qrImageUrl": "http://103.249.116.110:3004/qr/qr_1780908088557_doghrplgu",
        "template": "frame",
        "decodedQrCode": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
        "deeplink": "acbone://FUTE/external/transactions/v1/qrcode?timeStamp=1780908088558&qrCode=00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT%20CP%20TM%20DAU%20TU%20GAJA6011HO%20CHI%20MINH62350708591998060819THANH%20TOAN%20DON%20HANG63047130&signature=hQ5jWVmYrAAPpxyl%2F5X0dXLN9oZjot5JLAxIjcZNy2WisM0iVHiNyoqaVl6n%2F3TJgX9kgzbjRsTDxD2cFXTVU7pHlpCGDwWawZ4LHm1ZHnFw6yeneAySvoVsIDyUzxAgJZlW5%2B2ML4lVi4lJqZQOZviFz0fyFbtbbUNowg9MAkunqcJTKe%2FshRbEQ8%2BYXDHLm6BsysRN2mIhIlaaspL8BW1hvbJp0vzCmHHGe40PdE5QTk2YX56ug3fa3dgV5q7nHnxnql6FRkEpufnpTFiWO5KIUy70zviajLYbiIQoE0LL%2B%2Bs5kjaESWoVeixJ%2BGexD7ikmGU3YIWUXXlUYKdHbQ%3D%3D&redirectUrl=pay2s.vn",
        "signature": "hQ5jWVmYrAAPpxyl/5X0dXLN9oZjot5JLAxIjcZNy2WisM0iVHiNyoqaVl6n/3TJgX9kgzbjRsTDxD2cFXTVU7pHlpCGDwWawZ4LHm1ZHnFw6yeneAySvoVsIDyUzxAgJZlW5+2ML4lVi4lJqZQOZviFz0fyFbtbbUNowg9MAkunqcJTKe/shRbEQ8+YXDHLm6BsysRN2mIhIlaaspL8BW1hvbJp0vzCmHHGe40PdE5QTk2YX56ug3fa3dgV5q7nHnxnql6FRkEpufnpTFiWO5KIUy70zviajLYbiIQoE0LL++s5kjaESWoVeixJ+GexD7ikmGU3YIWUXXlUYKdHbQ==",
        "timeStamp": "1780908088558",
        "orderId": "123456",
        "amount": 10000,
        "status": "processing",
        "traceNumber": "QRPP2S26159194HHKZE",
        "qrData": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
        "transactionId": "0e907bd2-74d0-4c49-96ba-e64660736a2a",
        "requestID": "P2S-1780908087998-3hlh5c4og"
    }
}
```

| Trường | Mô tả |
|--------|-------|
| `code` | Mã lỗi: `00` = Thành công |
| `desc` | Mô tả: `Succeed` = Thành công |
| `qrDataURL` | Chuỗi QR mã (EMV-QR format) - dùng để sinh hình ảnh QR |
| `traceNumber` | Mã định danh duy nhất của QR được tạo ra |
| `transactionId` | Mã kiểm tra trạng thái QR |


---

## 💻 Code mẫu - Tạo QR cho từng đơn hàng

::: code-group

```php [PHP]
<?php
$bearerToken = "YOUR_BEARER_TOKEN";
$url = "http://api.pay2s.vn/api/vcb/v1/qr/create";

$data = [
    'orderId' => "123456",
    'amount' => 10000,
    'merchantName' => "CONG TY CO PHAN FUTE",
    'merchantId' => "091000000900000",
    'terminalId' => "60000000",
    'description' => "THANH TOAN DON HANG",
    'template' => "frame",
    'maskAccount' => true,
    'frameId' => 1
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . $bearerToken,
    "Content-Type: application/json"
]);

$response = curl_exec($ch);
$result = json_decode($response, true);

if ($result['code'] === '00') {
    $qrData = $result['data'];
    echo "QR Code: " . $qrData;
} else {
    echo "Error: " . $result['desc'];
}

curl_close($ch);
?>
```

```javascript [Node.js]
const axios = require('axios');

const bearerToken = "YOUR_BEARER_TOKEN";

const payload = {
  orderId: "123456",
  amount: 10000,
  merchantName: "CONG TY CO PHAN FUTE",
  merchantId: "091000000900000",
  terminalId: "60000000",
  description: "THANH TOAN DON HANG",
  template: "frame",
  maskAccount: true,
  frameId: 1
};

axios.post("http://api.pay2s.vn/api/vcb/v1/qr/create", payload, {
  headers: {
    "Authorization": `Bearer ${bearerToken}`,
    "Content-Type": "application/json"
  }
})
  .then(response => {
    if (response.data.code === '00') {
      console.log("QR Code:", response.data.data);
    } else {
      console.error("Error:", response.data.desc);
    }
  })
  .catch(err => console.error(err));
```

```python [Python]
import requests
import json

bearer_token = "YOUR_BEARER_TOKEN"

payload = {
    'orderId': "123456",
    'amount': 10000,
    'merchantName': "CONG TY CO PHAN FUTE",
    'merchantId': "091000000900000",
    'terminalId': "60000000",
    'description': "THANH TOAN DON HANG",
    'template': "frame",
    'maskAccount': True,
    'frameId': 1
}

headers = {
    "Authorization": f"Bearer {bearer_token}",
    "Content-Type": "application/json"
}

response = requests.post("http://api.pay2s.vn/api/vcb/v1/qr/create", 
                         data=json.dumps(payload), 
                         headers=headers)
result = response.json()

if result['code'] == '00':
    print("QR Code:", result['data'])
else:
    print("Error:", result['desc'])
```

:::

---

## ⚡ Best Practices

1. **Validate input**: Kiểm tra `amount > 0` trước khi request
2. **Error handling**: Kiểm tra `code` trước xử lý `data`
3. **Lưu QR data**: Lưu chuỗi QR vào database để regenerate ảnh nếu cần
4. **Timeout QR**: Đặt QR có hiệu lực 15-30 phút
5. **MaskAccount**: Sử dụng `maskAccount: true` để bảo mật thông tin
6. **Template**: Chọn `template: "frame"` để hiển thị với UI frame
7. **TerminalId**: Đảm bảo terminalId hợp lệ từ ngân hàng
8. **Retry logic**: Nếu request fail, retry sau 2-3 giây
9. **Database record**: Lưu `orderId`, `amount`, `qr_status` để tracking
10. **Rate limit**: Giới hạn số lượng request tạo QR mỗi phút

---

## 📌 Luồng tạo QR

```
1. Khách order → 2. Hệ thống tạo QR → 3. Hiển thị ảnh QR + frame → 
4. Khách scan QR → 5. Chọn ngân hàng → 6. Xác nhận → 
7. Thanh toán → 8. Webhook notify → 9. Cập nhật status = paid
```

---

> **Tiếp theo**: Xem [Webhook](/webhook/tai-lieu-ky-thuat.html) để nhận thông báo thanh toán tự động


---

Nguồn trang: https://docs.pay2s.vn/oneqr/kiem-tra-oneqr.html

# 🔍 Kiểm tra OneQR - Xem chi tiết QR

## 📱 Tổng quan

Để kiểm tra chi tiết một mã QR đã tạo, bạn gọi API **Retrieve QR Transaction** của OneQR. API này trả về đầy đủ thông tin giao dịch và trạng thái thanh toán.

---

## 🔌 Endpoint

```
GET http://api.pay2s.vn/api/{bank_code}/v1/qr/transactions/{transactionId}
```

| Tham số | Ví dụ | Mô tả |
|--------|-------|-------|
| `{bank_code}` | `vcb` | Mã ngân hàng (vcb = Vietcombank, acb = ACB Bank, ...) |
| `{transactionId}` | `0e907bd2-74d0-4c49-96ba-e64660736a2a` | Mã truy vấn (từ response tạo QR) |

---

## 📤 Request

### Headers

```json
{
  "Authorization": "Bearer <token>"
}
```

> **Lưu ý**: `<token>` là Bearer token được cấp từ Pay2S

### ✅ Ví dụ Request (cURL)

```bash
curl --location 'http://api.pay2s.vn/api/vcb/v1/qr/transactions/0e907bd2-74d0-4c49-96ba-e64660736a2a' \
  --header 'Authorization: Bearer YOUR_BEARER_TOKEN'
```

---

## 📥 Response

### ✅ Thành công (200 OK)

```json
{
  "success": true,
  "message": "QR transaction retrieved successfully",
  "data": {
    "transactionId": "0e907bd2-74d0-4c49-96ba-e64660736a2a",
    "merchantId": "091000000900000",
    "terminalId": "60199886",
    "orderId": "123456",
    "amount": 10000,
    "description": "THANH TOAN DON HANG",
    "exoDate": null,
    "qrData": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
    "status": "processing",
    "traceNumber": "QRPP2S26159194HHKZE",
    "bankCode": "978485",
    "responseCode": "00",
    "requestId": "P2S-1780908087998-3hlh5c4og",
    "vcbResponse": {
      "code": "00",
      "nodeIn": "0.0.0.0",
      "message": "Succeed",
      "nodeOut": "10.1.60.185",
      "subCode": "00",
      "checksum": "8DB582799A700B17FA56C2558CC8CCB1",
      "operation": "InitInvoice",
      "ccPayLoad": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
      "requestId": "P2S-1780908087999-3hlh5c4og",
      "paymentRef": "QRPP2S26159194HHKZE",
      "serverTime": "15:42:07 09/06/2026",
      "subMessage": "Succeed",
      "paymentName": "CT CP TM DAU TU GAJA"
    },
    "auditData": {
      "channel": "P2S",
      "channelId": "0.0.0.0",
      "channelTime": "2825600815142",
      "channelUser": "adi-payment",
      "channelUserBranch": "01"
    },
    "createdAt": "2025-06-08T09:41:28.000Z"
  }
}
```

### Giải thích Response

| Trường | Mô tả |
|--------|-------|
| `success` | Trạng thái: `true` = Lấy thông tin thành công |
| `transactionId` | Mã giao dịch duy nhất |
| `orderId` | ID đơn hàng |
| `amount` | Số tiền (VND) |
| `status` | Trạng thái: `processing` (chờ), `paid` (thanh toán), `cancelled` (hủy) |
| `qrData` | Chuỗi QR mã (EMV-QR format) |
| `traceNumber` | Mã định danh QR |
| `vcbResponse` | Chi tiết response từ ngân hàng |
| `createdAt` | Thời gian tạo QR |
| `paymentRef` | Mã tham chiếu thanh toán |

---

## 💻 Code mẫu - Kiểm tra QR

::: code-group

```php [PHP]
<?php
$bearerToken = "YOUR_BEARER_TOKEN";
$transactionId = "0e907bd2-74d0-4c49-96ba-e64660736a2a";
$url = "http://api.pay2s.vn/api/vcb/v1/qr/transactions/" . $transactionId;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . $bearerToken
]);

$response = curl_exec($ch);
$result = json_decode($response, true);

if ($result['success'] === true) {
    $txn = $result['data'];
    
    echo "Order ID: " . $txn['orderId'] . "\n";
    echo "Amount: " . $txn['amount'] . " VND\n";
    echo "Status: " . $txn['status'] . "\n";
    echo "Description: " . $txn['description'] . "\n";
    echo "Created: " . $txn['createdAt'] . "\n";
    
    // Kiểm tra thanh toán
    if ($txn['status'] === 'paid') {
        echo "✅ Thanh toán thành công!\n";
        echo "Payment Ref: " . $txn['paymentRef'] . "\n";
    } else if ($txn['status'] === 'processing') {
        echo "⏳ Chờ thanh toán...\n";
    } else if ($txn['status'] === 'cancelled') {
        echo "❌ QR đã bị hủy\n";
    }
} else {
    echo "Error: " . $result['message'];
}

curl_close($ch);
?>
```

```javascript [Node.js]
const axios = require('axios');

const bearerToken = "YOUR_BEARER_TOKEN";
const transactionId = "0e907bd2-74d0-4c49-96ba-e64660736a2a";

const url = `http://api.pay2s.vn/api/vcb/v1/qr/transactions/${transactionId}`;

axios.get(url, {
  headers: {
    "Authorization": `Bearer ${bearerToken}`
  }
})
  .then(response => {
    if (response.data.success === true) {
      const txn = response.data.data;
      
      console.log(`Order ID: ${txn.orderId}`);
      console.log(`Amount: ${txn.amount} VND`);
      console.log(`Status: ${txn.status}`);
      console.log(`Description: ${txn.description}`);
      console.log(`Created: ${txn.createdAt}`);
      
      // Check payment status
      if (txn.status === 'paid') {
        console.log("✅ Payment successful!");
        console.log(`Payment Ref: ${txn.paymentRef}`);
      } else if (txn.status === 'processing') {
        console.log("⏳ Waiting for payment...");
      } else if (txn.status === 'cancelled') {
        console.log("❌ QR was cancelled");
      }
    } else {
      console.error("Error:", response.data.message);
    }
  })
  .catch(err => console.error(err));
```

```python [Python]
import requests

bearer_token = "YOUR_BEARER_TOKEN"
transaction_id = "0e907bd2-74d0-4c49-96ba-e64660736a2a"

url = f"http://api.pay2s.vn/api/vcb/v1/qr/transactions/{transaction_id}"

headers = {
    "Authorization": f"Bearer {bearer_token}"
}

response = requests.get(url, headers=headers)
result = response.json()

if result['success'] == True:
    txn = result['data']
    
    print(f"Order ID: {txn['orderId']}")
    print(f"Amount: {txn['amount']} VND")
    print(f"Status: {txn['status']}")
    print(f"Description: {txn['description']}")
    print(f"Created: {txn['createdAt']}")
    
    # Check payment status
    if txn['status'] == 'paid':
        print("✅ Payment successful!")
        print(f"Payment Ref: {txn['paymentRef']}")
    elif txn['status'] == 'processing':
        print("⏳ Waiting for payment...")
    elif txn['status'] == 'cancelled':
        print("❌ QR was cancelled")
else:
    print("Error:", result['message'])
```

```java [Java]
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class RetrieveQRClient {
    
    public static void main(String[] args) throws Exception {
        String bearerToken = "YOUR_BEARER_TOKEN";
        String transactionId = "0e907bd2-74d0-4c49-96ba-e64660736a2a";
        
        String url = "http://api.pay2s.vn/api/vcb/v1/qr/transactions/" + transactionId;
        
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Authorization", "Bearer " + bearerToken);
        
        int responseCode = conn.getResponseCode();
        System.out.println("Response Code: " + responseCode);
        
        if (responseCode == 200) {
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();
            
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            
            // Parse and display JSON response
            System.out.println("Response: " + response.toString());
            
            // Note: For production, use a JSON library like Gson or Jackson
        }
    }
}
```

:::

---

## ⚡ Best Practices

1. **Cache transaction ID**: Lưu `transactionId` từ khi tạo QR để kiểm tra sau
2. **Poll status**: Gọi API này định kỳ để kiểm tra trạng thái thanh toán
3. **Error handling**: Kiểm tra `success` trước xử lý `data`
4. **Timeout**: Đặt timeout hợp lý (30-60 giây)
5. **Retry logic**: Nếu request fail, retry sau 2-3 giây
6. **Check payment status**: Luôn kiểm tra `status` trước cập nhật database
7. **Store response**: Lưu toàn bộ response để audit trail
8. **VCB Response**: Kiểm tra `vcbResponse.code` để xác nhận ngân hàng
9. **Avoid repeated calls**: Đừng gọi quá nhiều lần trong một vài giây
10. **Logging**: Log transactionId và status để debug

---

## 🔄 Luồng kiểm tra QR

```javascript
// 1. Tạo QR → nhận transactionId
// 2. Hiển thị QR cho khách hàng
// 3. Poll API kiểm tra status
const interval = setInterval(() => {
  getQRStatus(transactionId).then(status => {
    if (status === 'completed') {
      clearInterval(interval);
      console.log("✅ Payment completed");
      updateOrderStatus('paid');
    } else if (status === 'cancelled') {
      clearInterval(interval);
      console.log("❌ QR cancelled");
    }
  });
}, 2000); // Kiểm tra mỗi 2 giây
```

---

## 📊 Trạng thái QR

| Status | Mô tả | Hành động |
|--------|-------|----------|
| `processing` | Chờ thanh toán | Tiếp tục kiểm tra |
| `paid` | Đã thanh toán | Cập nhật order, gửi email |
| `cancelled` | QR bị hủy | Hiển thị lỗi, yêu cầu tạo QR mới |
| `expired` | QR hết hạn | Yêu cầu tạo QR mới |

---

> **Tiếp theo**: Xem [Danh sách OneQR](/oneqr/list-oneqr) để xem tất cả QR hoặc [Webhook](/webhook/tai-lieu-ky-thuat.html) để nhận thông báo tự động


---

Nguồn trang: https://docs.pay2s.vn/oneqr/list-oneqr.html

# 📋 Danh sách OneQR - Xem QR đã tạo

## 📱 Tổng quan

Để lấy danh sách các mã QR đã tạo, bạn gọi API **List QR Code** của OneQR. API này hỗ trợ phân trang để xem các QR được tạo trong quá khứ.

---

## 🔌 Endpoint

```
GET http://api.pay2s.vn/api/{bank_code}/v1/qr/list
```

| Tham số | Ví dụ | Mô tả |
|--------|-------|-------|
| `{bank_code}` | `vcb` | Mã ngân hàng (vcb = Vietcombank, acb = ACB Bank, ...) |

---

## 📤 Request

### Headers

```json
{
  "Authorization": "Bearer <token>"
}
```

> **Lưu ý**: `<token>` là Bearer token được cấp từ Pay2S

### Query Parameters

| Tham số | Loại | Bắt buộc | Mô tả |
|--------|------|---------|-------|
| `limit` | Number | - | Số lượng bản ghi trả về (default: 20, max: 100) |
| `offset` | Number | - | Vị trí bắt đầu (dùng cho phân trang, default: 0) |

### ✅ Ví dụ Request (cURL)

```bash
curl --location 'http://api.pay2s.vn/api/vcb/v1/qr/list?limit=20&offset=0' \
  --header 'Authorization: Bearer YOUR_BEARER_TOKEN'
```

---

## 📥 Response

### ✅ Thành công (200 OK)

```json
{
  "success": true,
  "message": "QR list retrieved successfully",
  "data": [
    {
      "transactionId": "0e907bd2-74d0-4c49-96ba-e64660736a2a",
      "merchantId": "091000000900000",
      "terminalId": "60199886",
      "orderId": "123456",
      "amount": 10000,
      "description": "THANH TOAN DON HANG",
      "exoDate": null,
      "qrData": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
      "status": "processing",
      "traceNumber": "QRPP2S26159194HHKZE",
      "requestId": "P2S-1780908087998-3hlh5c4og",
      "bankCode": "978485",
      "responseCode": "00",
      "createdAt": "2025-06-08T10:58.000Z"
    },
    {
      "transactionId": "72c00721-8ae2-4192-b7e3-7dfc0773d33f",
      "merchantId": "091000000900000",
      "terminalId": "60199886",
      "orderId": "6844",
      "amount": 2000,
      "description": "HMD6085",
      "exoDate": null,
      "qrData": "00020101021238630010A000000727013300069704360119QRPP2S26159194HHKZE0208QRIBFTTA5204732153037045405100005802VN5920CT CP TM DAU TU GAJA6011HO CHI MINH62350708591998060819THANH TOAN DON HANG63047130",
      "status": "cancelled",
      "traceNumber": "QRPP2S26159194HHKZE",
      "requestId": "P2S-1780994702237-vAzRiKxO",
      "bankCode": "978485",
      "responseCode": "00",
      "createdAt": "2025-06-08T10:25.000Z"
    }
  ],
  "total": 8,
  "limit": 20,
  "offset": 0
}
```

### Giải thích Response

| Trường | Mô tả |
|--------|-------|
| `success` | Trạng thái: `true` = Lấy danh sách thành công |
| `message` | Mô tả: `QR list retrieved successfully` |
| `data` | Mảng danh sách QR codes |
| `transactionId` | Mã giao dịch duy nhất |
| `orderId` | ID đơn hàng |
| `amount` | Số tiền (VND) |
| `status` | Trạng thái: `processing`, `cancelled`, `completed` |
| `traceNumber` | Mã định danh QR |
| `createdAt` | Thời gian tạo QR |


---

## 💻 Code mẫu - Lấy danh sách QR

::: code-group

```php [PHP]
<?php
$bearerToken = "YOUR_BEARER_TOKEN";
$limit = 20;
$offset = 0;
$url = "http://api.pay2s.vn/api/vcb/v1/qr/list?limit=" . $limit . "&offset=" . $offset;

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . $bearerToken
]);

$response = curl_exec($ch);
$result = json_decode($response, true);

if ($result['success'] === true) {
    $qrList = $result['data'];
    echo "Found " . count($qrList) . " QR codes\n";
    
    foreach ($qrList as $qr) {
        echo "Order: " . $qr['orderId'] . 
             " | Amount: " . $qr['amount'] . 
             " | Status: " . $qr['status'] . "\n";
    }
    
    $pagination = $result['pagination'];
    echo "Total: " . $pagination['total'] . " QR codes\n";
} else {
    echo "Error: " . $result['message'];
}

curl_close($ch);
?>
```

```javascript [Node.js]
const axios = require('axios');

const bearerToken = "YOUR_BEARER_TOKEN";
const limit = 20;
const offset = 0;

const url = `http://api.pay2s.vn/api/vcb/v1/qr/list?limit=${limit}&offset=${offset}`;

axios.get(url, {
  headers: {
    "Authorization": `Bearer ${bearerToken}`
  }
})
  .then(response => {
    if (response.data.success === true) {
      const qrList = response.data.data;
      console.log(`Found ${qrList.length} QR codes`);
      
      qrList.forEach(qr => {
        console.log(`Order: ${qr.orderId} | Amount: ${qr.amount} | Status: ${qr.status}`);
      });
      
      const pagination = response.data.pagination;
      console.log(`Total: ${pagination.total} QR codes`);
    } else {
      console.error("Error:", response.data.message);
    }
  })
  .catch(err => console.error(err));
```

```python [Python]
import requests

bearer_token = "YOUR_BEARER_TOKEN"
limit = 20
offset = 0

url = f"http://api.pay2s.vn/api/vcb/v1/qr/list?limit={limit}&offset={offset}"

headers = {
    "Authorization": f"Bearer {bearer_token}"
}

response = requests.get(url, headers=headers)
result = response.json()

if result['success'] == True:
    qr_list = result['data']
    print(f"Found {len(qr_list)} QR codes")
    
    for qr in qr_list:
        print(f"Order: {qr['orderId']} | Amount: {qr['amount']} | Status: {qr['status']}")
    
    pagination = result['pagination']
    print(f"Total: {pagination['total']} QR codes")
else:
    print("Error:", result['message'])
```

```java [Java]
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class ListQRClient {
    
    public static void main(String[] args) throws Exception {
        String bearerToken = "YOUR_BEARER_TOKEN";
        int limit = 20;
        int offset = 0;
        
        String url = "http://api.pay2s.vn/api/vcb/v1/qr/list?limit=" + limit + "&offset=" + offset;
        
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Authorization", "Bearer " + bearerToken);
        
        int responseCode = conn.getResponseCode();
        System.out.println("Response Code: " + responseCode);
        
        if (responseCode == 200) {
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();
            
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            
            // Parse JSON response
            System.out.println("Response: " + response.toString());
        }
    }
}
```

:::

---

## ⚡ Best Practices

1. **Pagination**: Sử dụng `limit` và `offset` để lấy dữ liệu từng trang
2. **Limit cách hợp lý**: Không nên lấy quá nhiều bản ghi (max: 100)
3. **Cache kết quả**: Lưu danh sách QR vào cache để tránh request quá nhiều
4. **Filter by status**: Lọc QR theo status (`processing`, `cancelled`, `paid`)
5. **Sort by date**: Sắp xếp theo `createdAt` để xem QR mới nhất trước
6. **Track total**: Lưu `total` từ pagination để biết có bao nhiêu QR
7. **Error handling**: Kiểm tra `success` trước xử lý `data`
8. **Timeout**: Đặt timeout hợp lý (30-60 giây)
9. **Retry logic**: Nếu request fail, retry sau 2-3 giây
10. **Logging**: Log danh sách QR được lấy để audit

---

## 📌 Phân trang - Pagination

Để lấy dữ liệu theo trang, sử dụng `limit` và `offset`:

```javascript
// Trang 1: offset = 0, limit = 20
const page1 = `/qr/list?limit=20&offset=0`;

// Trang 2: offset = 20, limit = 20
const page2 = `/qr/list?limit=20&offset=20`;

// Trang 3: offset = 40, limit = 20
const page3 = `/qr/list?limit=20&offset=40`;
```

**Công thức**: `offset = (page - 1) * limit`

---

## 🔍 Filter QR theo Status

```bash
# Lấy danh sách QR đang xử lý
curl 'http://api.pay2s.vn/api/vcb/v1/qr/list?limit=20&offset=0' \
  -H 'Authorization: Bearer TOKEN'

# Sau đó filter trong code:
# status = 'processing' (chờ thanh toán)
# status = 'paid' (đã thanh toán)
# status = 'cancelled' (đã hủy)
```

---

> **Tiếp theo**: Xem [Tạo OneQR](/oneqr/khoi-tao-oneqr) để tạo QR mới hoặc [Hủy OneQR](/oneqr/huy-oneqr) để hủy QR hoặc [Kiểm tra OneQR](/oneqr/kiem-tra-oneqr) để xem chi tiết

