# API tra cứu trạng thái phiên

**Endpoint:** `POST https://payment.pay2s.vn/api/card-popup-session.php`

**Content-Type:** `application/json`. Gọi từ backend với bốn header HMAC trong [Xác thực](/docs/card-popup/authentication/). Cả ba thao tác dùng cùng URL, phân biệt bằng `action`. Không mở URL API trực tiếp bằng trình duyệt.

Các ví dụ dưới đây dùng dữ liệu minh họa, không phải phiên thanh toán đang hoạt động. Timestamp, ID và token phải lấy từ response thật.

## Request
```json
{
  "action": "status",
  "orderId": "ORDER001"
}
```

| Trường | Kiểu | Bắt buộc | Ý nghĩa |
| --- | --- | --- | --- |
| action | string | Có | status |
| orderId | string | Có | Mã đơn đã tạo bằng cùng phạm vi bộ khóa |

Không cần truyền connectionId lại: phiên đã ghim kết nối. Khóa cửa hàng khác không đọc được đơn này.

## Response HTTP 200
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "paid",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": true
  }
}
```

| Trường data | Kiểu | Ý nghĩa |
| --- | --- | --- |
| connectionId | integer/null | Kết nối đã ghim; phiên cũ có thể null |
| sessionId | string | ID phiên 64 ký tự hex |
| orderId | string | Mã đơn merchant |
| environment | string | sandbox/production; sandbox không giao hàng thật |
| status | string | Trạng thái nghiệp vụ, xem bảng dưới |
| amount | integer | Số tiền gốc của đơn, VND |
| merchantOrigin | string | Origin được phép nhúng phiên |
| expiresAt | integer | Unix giây, không phải thời hạn chứng minh giao dịch chưa thu tiền |
| launched | boolean | Phiên đã được đánh dấu mở; không chứng minh provider đã nhận form hoặc đã thu tiền |

## Tất cả trạng thái

| status | Cách xử lý |
| --- | --- |
| ready | Chưa mở form; có thể gọi SDK với phiên này |
| pending | Đang xác minh; không giao hàng hay tự tạo giao dịch thay thế |
| paid | Đã xác minh; backend merchant đối chiếu đơn/số tiền/môi trường và hoàn tất đúng một lần |
| failed | Lần thanh toán thất bại; không đồng nghĩa khách chủ động hủy |
| cancelled | Đơn được ghi nhận hủy |
| review | Cần đối soát; không tự kết luận thành công/thất bại |
| expired | Phiên chưa mở đã hết hạn |

### Ví dụ ready
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "ready",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": false
  }
}
```

### Ví dụ pending
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "pending",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": true
  }
}
```

### Ví dụ failed
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "failed",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": true
  }
}
```

### Ví dụ cancelled
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "cancelled",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": true
  }
}
```

### Ví dụ review
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "review",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": true
  }
}
```

### Ví dụ expired
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "status": "expired",
    "amount": 100000,
    "merchantOrigin": "https://shop.example.com",
    "expiresAt": 1792314900,
    "launched": false
  }
}
```

## Cập nhật và độ trễ

IPN/cron cập nhật trạng thái. Với phiên đã mở và còn pending, status có truy vấn đối soát ePay dự phòng: claim bằng last_checked_at, giới hạn tối thiểu 60 giây giữa các lần inquiry từ đường status. Không gọi ePay mỗi lần frontend poll. Lỗi inquiry không biến đơn thành failed; trạng thái có thể vẫn pending.

SDK gọi backend merchant khoảng 4 giây/lần, dừng khi có trạng thái cuối hoặc sau 30 phút. Tổng giới hạn API payment hiện là 30 request/phút/IP cho endpoint này, dùng chung ba action: backend merchant nên cache/gom request khi có nhiều tab/người dùng. Không nên để mọi tab cùng gọi độc lập không giới hạn.

Phiên đã mở có thể pending sau expiresAt. Thành công muộn có thể chuyển review. API trả HTTP 200 cùng status=failed/cancelled vẫn là tra cứu thành công, không phải lỗi HTTP.

## Code PHP, Node.js, Python

Tải client tương ứng và đặt cạnh file ví dụ: [PHP](/downloads/card-popup/client.php), [Node.js](/downloads/card-popup/client.mjs), [Python](/downloads/card-popup/pay2s_client.py). Cấu hình biến môi trường theo [Xác thực](/docs/card-popup/authentication/). Client ký đúng body gửi đi, kiểm tra HTTP và trả về phần `data`.

### PHP 8.1+ với cURL
```php
<?php
require __DIR__.'/client.php';
$payload=json_decode('{"action":"status","orderId":"ORDER001"}',true,32,JSON_THROW_ON_ERROR);
try {
    $data=pay2s($payload);
    // Xử lý $data tại backend. Không log popupUrl/token hoặc khóa.
} catch (Throwable $e) {
    // Giữ orderId nếu có; tra cứu khi chưa rõ kết quả, không tạo đơn mới tự động.
    throw $e;
}
```

### Node.js 20+ (ES modules)
```js
import {pay2s} from './client.mjs';
const payload={
  "action": "status",
  "orderId": "ORDER001"
};
const data=await pay2s(payload);
// Xử lý data ở backend; chỉ trả data phiên cho đúng người mua.
```

### Python 3.10+ (thư viện chuẩn)
```python
import json
from pay2s_client import pay2s
payload = json.loads(r'''{
  "action": "status",
  "orderId": "ORDER001"
}''')
data = pay2s(payload)
# Xử lý data ở backend, không ghi token/khóa vào log.
```

### cURL

Lưu body request ở trên thành `request.json`. Tạo `TIMESTAMP` và `SIGNATURE` trên đúng byte của file theo trang xác thực; không ký một body khác.
```bash
curl --request POST 'https://payment.pay2s.vn/api/card-popup-session.php' \
  --header 'Content-Type: application/json' \
  --header "X-Pay2S-Partner: $PAY2S_PARTNER_CODE" \
  --header "X-Pay2S-Access-Key: $PAY2S_ACCESS_KEY" \
  --header "X-Pay2S-Timestamp: $TIMESTAMP" \
  --header "X-Pay2S-Signature: $SIGNATURE" \
  --data-binary @request.json
```


## Response lỗi

### HTTP 403
```json
{
  "status": false,
  "success": false,
  "message": "Tích hợp popup chưa được bật.",
  "data": []
}
```

### HTTP 404
```json
{
  "status": false,
  "success": false,
  "message": "Không tìm thấy đơn.",
  "data": []
}
```

### HTTP 409
```json
{
  "status": false,
  "success": false,
  "message": "Kết nối không tồn tại, đã tắt hoặc không thuộc phạm vi bộ khóa.",
  "data": {
    "reference": "MGP-example-reference"
  }
}
```

### HTTP 429
```json
{
  "status": false,
  "success": false,
  "message": "Quá nhiều yêu cầu.",
  "data": []
}
```

### HTTP 503
```json
{
  "status": false,
  "success": false,
  "message": "Chưa thể xử lý thanh toán thẻ. Vui lòng thử lại sau.",
  "data": {
    "reference": "MGP-example-reference"
  }
}
```

Không dựa vào chuỗi `message` để quyết định thanh toán thành công/thất bại. `data` có thể là mảng rỗng hoặc object chứa `reference`. Xem [bảng HTTP và cách thử lại](/docs/card-popup/authentication/).

## Phiên hosted

Response data bổ sung checkoutMode (sdk hoặc hosted) và returnUrl (URL đã ký với hosted, null với sdk). Khi khách trở lại returnUrl, backend tra cứu bằng orderId đã lưu; không coi việc quay về hoặc query của trình duyệt là bằng chứng thanh toán.
