Kiểm tra OneQR - Xem chi tiết QR

Nội dung Markdown đầy đủ của trang tài liệu.

# 🔍 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": "da58fde344a1e0c9280207e1143c6f40",
    "merchantId": "DEMO_MERCHANT",
    "terminalId": "DEMO_TERMINAL",
    "orderId": "DEMO17916599748852455",
    "amount": 100000,
    "description": "THANHTOANDEMO",
    "exoDate": null,
    "qrData": "https://developer.example.com/payment/?qr=da58fde344a1e0c9280207e1143c6f40",
    "status": "processing",
    "traceNumber": "DEMOQR50269d422b61",
    "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": "https://developer.example.com/payment/?qr=da58fde344a1e0c9280207e1143c6f40",
      "requestId": "P2S-1780908087999-3hlh5c4og",
      "paymentRef": "DEMOQR50269d422b61",
      "serverTime": "15:42:07 09/06/2026",
      "subMessage": "Succeed",
      "paymentName": "CUA HANG DEMO"
    },
    "auditData": {
      "channel": "P2S",
      "channelId": "0.0.0.0",
      "channelTime": "2825600815142",
      "channelUser": "adi-payment",
      "channelUserBranch": "01"
    },
    "createdAt": "2026-10-10T19:19:34.931Z"
  }
}
```

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


## Request và response đối chiếu ngày 11/10/2026

Các mẫu dưới đây giữ cấu trúc trường và kiểu dữ liệu. Khóa, chữ ký, URL phiên đã được thay bằng placeholder; danh sách chỉ giữ tối đa hai phần tử và dữ liệu ảnh/PDF/XML dài được rút gọn. Không sao chép placeholder để gọi API thật.

### OneQR · Truy vấn trạng thái

<div class="doc-api-actions" data-doc-api-actions><a class="try-button" data-doc-playground href="/playground/?api=oneqr-detail">Thử API trong Playground ↗</a><a class="try-button experience-button" data-doc-experience hidden>Trải nghiệm ↗</a></div>

**Mô phỏng; chưa xác minh với API nhà cung cấp.** Giữ mô phỏng theo cấu hình demo. Chưa có API OneQR thật để xác minh lại hợp đồng nhà cung cấp.

`GET /api/{bank_code}/v1/qr/transactions/{transactionId}`

Tham số path/query (không gửi JSON body):

```json
{
  "bank_code": "vcb",
  "transactionId": "LAST_QR"
}
```

HTTP 200. Response:

```json
{
  "success": true,
  "message": "QR transaction retrieved successfully",
  "data": {
    "transactionId": "da58fde344a1e0c9280207e1143c6f40",
    "merchantId": "DEMO_MERCHANT",
    "terminalId": "DEMO_TERMINAL",
    "orderId": "DEMO17916599748852455",
    "amount": 100000,
    "description": "THANHTOANDEMO",
    "exoDate": null,
    "qrData": "https://developer.example.com/payment/?qr=da58fde344a1e0c9280207e1143c6f40",
    "status": "processing",
    "traceNumber": "DEMOQR50269d422b61",
    "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": "https://developer.example.com/payment/?qr=da58fde344a1e0c9280207e1143c6f40",
      "requestId": "P2S-1780908087999-3hlh5c4og",
      "paymentRef": "DEMOQR50269d422b61",
      "serverTime": "15:42:07 09/06/2026",
      "subMessage": "Succeed",
      "paymentName": "CUA HANG DEMO"
    },
    "auditData": {
      "channel": "P2S",
      "channelId": "0.0.0.0",
      "channelTime": "2825600815142",
      "channelUser": "adi-payment",
      "channelUserBranch": "01"
    },
    "createdAt": "2026-10-10T19:19:34.931Z"
  }
}
```