# API tạo phiên thanh toán thẻ

**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": "create",
  "connectionId": 12,
  "orderId": "ORDER001",
  "amount": 100000,
  "cardBrand": "visa",
  "buyer": {
    "firstName": "An",
    "lastName": "Nguyen",
    "email": "buyer@example.com"
  },
  "merchantOrigin": "https://shop.example.com",
  "webhookUrl": "https://shop.example.com/pay2s/webhook",
  "sandboxOnly": true
}
```

| Trường | Kiểu | Bắt buộc | Quy tắc |
| --- | --- | --- | --- |
| action | string | Không | create; bỏ qua cũng là tạo phiên |
| connectionId | integer | Không | Số nguyên dương đã lưu; bỏ qua để dùng mặc định được gán |
| orderId | string | Có | 1–128 ký tự chữ/số ASCII hoặc . _ : - |
| amount | integer | Có | VND nguyên, 2000–2147483646; không gửi chuỗi hoặc số thập phân |
| cardBrand | string | Có | visa, mastercard, jcb, amex, unionpay |
| buyer | object | Có | Thông tin người trả; không chứa số thẻ/CVV |
| buyer.firstName | string | Có | 1–30 ký tự sau trim: chữ/dấu, khoảng trắng, dấu chấm/gạch nối |
| buyer.lastName | string | Có | Như firstName |
| buyer.email | string | Có | Email hợp lệ, tối đa 40 byte theo kiểm tra backend |
| merchantOrigin | string | Có | Origin HTTPS của trang nhúng, không có path/query/fragment |
| webhookUrl | string | Có | HTTPS công khai; không được trỏ tới mạng nội bộ |
| sandboxOnly | boolean | Không | Mặc định false; true bắt buộc kết nối sandbox, không tự tìm kết nối sandbox khác |

## Response HTTP 200
```json
{
  "status": true,
  "success": true,
  "message": "OK",
  "data": {
    "connectionId": 12,
    "sessionId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "orderId": "ORDER001",
    "environment": "sandbox",
    "expiresAt": 1792314900,
    "popupUrl": "https://payment.pay2s.vn/sdk/card-popup.html#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
  }
}
```

| Trường data | Kiểu | Ý nghĩa |
| --- | --- | --- |
| connectionId | integer/null | Kết nối ghim vào phiên mới; phiên cũ trước bản ghim có thể null |
| sessionId | string | 64 ký tự hex, định danh phiên để đối chiếu |
| orderId | string | Mã đơn merchant đã gửi |
| environment | string | sandbox hoặc production |
| expiresAt | integer | Unix giây; phiên mới có thời hạn 15 phút |
| popupUrl | string | URL bridge và capability token trong fragment; đưa nguyên vẹn cho SDK, không log/chia sẻ công khai |

`status:true` là tạo phiên thành công, **không phải đã thanh toán**. Trả phần data về đúng người mua đã xác thực rồi gọi SDK. Không gửi secret xuống trình duyệt.

## Kết nối, chống trùng và thử lại

Khóa tài khoản có thể chọn kết nối của tài khoản, kể cả chưa có cửa hàng. Khóa cửa hàng chỉ chọn kết nối được gán cho cửa hàng. Phiên mới ghim connectionId và bộ khóa; thay mặc định không đổi phiên. Tắt kết nối hoặc đổi bộ khóa trước khi mở sẽ bị từ chối, không tự chuyển sang kết nối khác. Giao dịch đã gửi dùng bộ khóa đã lưu để đối soát.

Cùng phạm vi khóa + orderId + nội dung chuẩn hóa giống nhau trả phiên cũ. Thay connectionId/số tiền/thẻ/buyer/origin/webhook/sandboxOnly sẽ xung đột. Đơn ngoài luồng popup không chuyển ngầm sang luồng này.

Timeout: giữ nguyên mã đơn và nội dung, tra cứu hoặc gửi lại cùng request. Không tự tạo mã mới. Mở lại/tải lại không gửi lại form ePay. Phiên cũ đã hết hạn vẫn là phiên cũ khi gửi lại cùng orderId. Chỉ tạo đơn mới sau khi ứng dụng xử lý rõ trạng thái đơn trước.

Tài khoản cần gói Basic còn hạn hoặc demo, kết nối bật, phương thức thẻ bật và loại thẻ được cho phép. Production còn cần phê duyệt website/cửa hàng; demo không dùng production. Không tự xuất HĐĐT trong API này.

## 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":"create","connectionId":12,"orderId":"ORDER001","amount":100000,"cardBrand":"visa","buyer":{"firstName":"An","lastName":"Nguyen","email":"buyer@example.com"},"merchantOrigin":"https://shop.example.com","webhookUrl":"https://shop.example.com/pay2s/webhook","sandboxOnly":true}',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": "create",
  "connectionId": 12,
  "orderId": "ORDER001",
  "amount": 100000,
  "cardBrand": "visa",
  "buyer": {
    "firstName": "An",
    "lastName": "Nguyen",
    "email": "buyer@example.com"
  },
  "merchantOrigin": "https://shop.example.com",
  "webhookUrl": "https://shop.example.com/pay2s/webhook",
  "sandboxOnly": true
};
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": "create",
  "connectionId": 12,
  "orderId": "ORDER001",
  "amount": 100000,
  "cardBrand": "visa",
  "buyer": {
    "firstName": "An",
    "lastName": "Nguyen",
    "email": "buyer@example.com"
  },
  "merchantOrigin": "https://shop.example.com",
  "webhookUrl": "https://shop.example.com/pay2s/webhook",
  "sandboxOnly": true
}''')
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 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/).
