API hóa đơn điện tử
✧ Mở bằng AI
API dành cho server của khách hàng tạo nháp, gửi nháp tới nhà cung cấp, phát hành và tra cứu hóa đơn điện tử qua kết nối đã cấu hình trên Pay2S. API này không tạo link thanh toán, không cộng tiền và không tự xác minh đơn hàng bên ngoài đã thanh toán.
Chuẩn bị
- Tài khoản chính có gói Basic trở lên và được bật tính năng HĐĐT.
- Trong Xuất hóa đơn tự động, tạo kết nối, kiểm tra đăng nhập nhà cung cấp, đồng bộ mẫu và chọn mẫu mặc định. Nếu truyền
store_id, cửa hàng phải được gán mẫu thuộc kết nối đó. - Lấy Partner Code, Access Key, Secret Key của tài khoản chính Pay2S (cùng bộ khóa dùng tích hợp thanh toán). Không dùng khóa Partner API độc lập, khóa nhân viên/cửa hàng hoặc token đăng nhập Dashboard.
- Dùng kết nối Sandbox để kiểm tra. Tài khoản demo đã cấu hình có thể gọi các thao tác trên kết nối Sandbox; quyền thực tế phụ thuộc tài khoản và kết nối. Production cần được Pay2S bật quyền phát hành thật.
Không gửi thông tin đăng nhập nhà cung cấp hóa đơn trong request. Chỉ giữ Secret Key ở backend của bạn.
Endpoint và xác thực
Tất cả thao tác dùng:
POST https://api.pay2s.vn/api/v1/einvoices
Content-Type: application/json
X-Partner-Code: YOUR_PARTNER_CODE
X-Access-Key: YOUR_ACCESS_KEY
X-Timestamp: UNIX_SECONDS
X-Signature: LOWERCASE_HMAC_SHA256_HEX
X-Timestamp là Unix timestamp 10 chữ số, lệch giờ server tối đa 300 giây. JSON tối đa 256 KiB. Giới hạn 60 request/phút/tài khoản, tính chung các thao tác; HTTP 429 thì đợi phút kế tiếp.
Chuỗi ký gồm 4 dòng, ngăn bằng ký tự LF (\n), không thêm xuống dòng cuối:
{timestamp}
POST
/api/v1/einvoices
{sha256_hex_cua_nguyen_body_JSON}
signature = HMAC-SHA256(secret_key, chuỗi_ký), xuất hex chữ thường. Ký đúng chuỗi JSON sẽ gửi; không serialize lại body sau khi ký. Timestamp và chữ ký nằm ở header, không nằm trong JSON. Không thêm query string vào endpoint.
Node.js
import { createHash, createHmac } from 'node:crypto';
async function einvoice(payload) {
const path = '/api/v1/einvoices';
const body = JSON.stringify(payload);
const timestamp = String(Math.floor(Date.now() / 1000));
const digest = createHash('sha256').update(body, 'utf8').digest('hex');
const canonical = `${timestamp}\nPOST\n${path}\n${digest}`;
const signature = createHmac('sha256', process.env.PAY2S_SECRET_KEY)
.update(canonical, 'utf8').digest('hex');
const response = await fetch(`https://api.pay2s.vn${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Partner-Code': process.env.PAY2S_PARTNER_CODE,
'X-Access-Key': process.env.PAY2S_ACCESS_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature
},
body,
signal: AbortSignal.timeout(90000)
});
return { httpStatus: response.status, result: await response.json() };
}
console.log(await einvoice({ action: 'connections' }));
PHP cURL
function einvoice(array $payload): array {
$path = '/api/v1/einvoices';
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$timestamp = (string)time();
$canonical = $timestamp . "\nPOST\n" . $path . "\n" . hash('sha256', $body);
$signature = hash_hmac('sha256', $canonical, getenv('PAY2S_SECRET_KEY'));
$ch = curl_init('https://api.pay2s.vn' . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Partner-Code: ' . getenv('PAY2S_PARTNER_CODE'),
'X-Access-Key: ' . getenv('PAY2S_ACCESS_KEY'),
'X-Timestamp: ' . $timestamp,
'X-Signature: ' . $signature,
],
]);
$response = curl_exec($ch);
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($response === false) throw new RuntimeException($error);
return ['httpStatus' => $httpStatus, 'result' => json_decode($response, true, 512, JSON_THROW_ON_ERROR)];
}
1. Lấy danh sách kết nối
{
"action": "connections"
}
Trả về kết nối đang hoạt động thuộc tài khoản đã xác thực, không trả mật khẩu/token của nhà cung cấp:
{
"status": true,
"data": {
"connections": [
{
"id": 14,
"name": "Mắt Bão eInvoice dùng thử",
"provider": "matbao_hddt",
"environment": "sandbox"
},
{
"id": 25,
"name": "MISA meInvoice dùng thử",
"provider": "misa_meinvoice",
"environment": "sandbox"
}
]
}
}
Các ID trong ví dụ là minh họa; dùng ID thực tế từ API. ID là số nguyên JSON ở request.
2. Tạo nháp tại Pay2S
{
"action": "create_draft",
"connection_id": 14,
"environment": "sandbox",
"external_ref": "DEV_d33eb1382b55f87313b6840bff80f880_17916599753206049db",
"auto_submit": false,
"auto_publish": false,
"buyer_contact_name": "Bán cho người tiêu dùng",
"payment_method": "TM/CK",
"items": [
{
"code": "DEMO01",
"name": "Sản phẩm thử",
"unit": "Cái",
"quantity": 2,
"unit_price": 100000,
"tax_rate": 10
}
]
}
Thành công lần đầu: HTTP 201. Chưa gọi nhà cung cấp và chưa phát hành.
{
"status": true,
"message": "Đã tạo bản nháp Sandbox.",
"data": {
"id": 2245,
"status": "draft",
"external_ref": "DEV_d33eb1382b55f87313b6840bff80f880_17916599753206049db"
}
}
Lưu data.id làm invoice_id cho các bước sau.
| Trường | Quy định |
|---|---|
connection_id |
Bắt buộc, ID kết nối đang hoạt động của tài khoản |
environment |
Bắt buộc sandbox hoặc production, phải khớp kết nối |
external_ref |
Bắt buộc, mã đơn riêng của bạn; 1–100 ký tự chữ/số/_/-, phân biệt hoa thường |
store_id |
Tùy chọn, số nguyên; bỏ trống hoặc 0 để dùng mẫu mặc định của kết nối |
buyer_name |
Tên đơn vị mua hàng, tối đa 400 ký tự |
buyer_tax_code |
Mã số thuế, tối đa 14 ký tự |
buyer_citizen_id |
CCCD, tối đa 12 ký tự |
buyer_contact_name |
Họ tên người mua, tối đa 100 ký tự |
buyer_address |
Địa chỉ, tối đa 400 ký tự |
buyer_phone |
Tối đa 20 ký tự |
buyer_email |
Email hợp lệ, tối đa 50 ký tự |
payment_method |
Mặc định TM/CK, tối đa 50 ký tự |
note |
Tối đa 255 ký tự |
items |
Mảng 1–100 dòng |
confirm_production |
Boolean true bắt buộc khi tạo nháp Production; vẫn chưa phát hành |
Phải có buyer_name hoặc buyer_contact_name. Thông tin người mua dùng cùng quy tắc Dashboard: doanh nghiệp cần đủ tên đơn vị, MST, địa chỉ; cá nhân cần họ tên và địa chỉ. Thiếu bộ thông tin định danh đầy đủ thì tên hiển thị chuyển thành Bán cho người tiêu dùng, bỏ thông tin định danh rời rạc. Đọc và kiểm tra dữ liệu nháp bằng detail trước khi phát hành.
| Trường dòng hàng | Quy định |
|---|---|
name |
Tên hàng bắt buộc; tối đa 500 ký tự |
code |
Mã hàng tùy chọn, tối đa 50 ký tự |
unit |
Đơn vị tính tùy chọn, tối đa 50 ký tự |
quantity |
Số JSON > 0, làm tròn 4 chữ số thập phân |
unit_price |
Số JSON ≥ 0, làm tròn 2 chữ số thập phân |
tax_rate |
Bắt buộc, giá trị hệ thống nhận: -2, -1, 0, 3.5, 5, 8, 10; phải phù hợp loại mẫu và nhà cung cấp |
direct_vat_rate |
Tùy chọn: 1, 2, 3, 5; dùng khi giảm thuế cho mẫu hóa đơn bán hàng trực tiếp |
vat_reduction_eligible |
Boolean, mặc định false; hệ thống kiểm tra điều kiện/mẫu khi áp dụng |
-1 là không chịu thuế, -2 là không kê khai tính nộp thuế. Các mã này là quy ước dữ liệu API; chọn đúng theo nghiệp vụ và mẫu đang dùng. Mỗi số và giá trị mỗi dòng tối đa 10^12. Tổng tiền do backend tính, không nhận tổng do client tự khai. Không truyền owner_user_id, user_id, source_order_id, template_id, provider, credentials hoặc idempotency_key.
Chống trùng và timeout
external_refduy nhất theo tài khoản chính, xuyên các kết nối/môi trường. Nên đặt tiền tố khác nhau cho đơn Sandbox và Production.- Cùng mã và dữ liệu: trả lại cùng ID, HTTP 200,
duplicate: true. Không tạo thêm hóa đơn. - Cùng mã nhưng thay đổi dữ liệu/kết nối: HTTP 409. Không tự đổi mã đơn để vượt lỗi này.
- Đã xóa nháp trên Dashboard thì mã cũ không được tạo lại. Dùng mã mới chỉ khi bạn thực sự muốn tạo hóa đơn thay thế.
- Nếu timeout khi
create_draft, ký lại request với timestamp mới nhưng giữ nguyên mã và dữ liệu. Các giá trị số nên giữ nguyên cách biểu diễn giữa các lần gọi (ví dụ2khác2.0trong dấu vân tay dữ liệu). - API có khóa xử lý đồng thời; HTTP 409 “đang được xử lý” thì đợi vài giây rồi gọi lại.
3. Gửi nháp sang nhà cung cấp
{
"action": "submit_draft",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
Dùng logic tạo nháp đã có trên Dashboard. Bản nháp đã có mã nhà cung cấp sẽ không được tạo lại. Đây chưa phải thao tác ký/phát hành. Kết quả và khả năng tạo nháp phụ thuộc nhà cung cấp.
4. Phát hành
Sau khi gửi nháp thành công và đã kiểm tra thông tin:
{
"action": "publish",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
Với Production, cả bước tạo/gửi nháp/phát hành đều cần "environment":"production" và "confirm_production":true, đúng kết nối Production và quyền phát hành của tài khoản. API không tự chuyển môi trường và không tự bật quyền.
publish thực hiện ký/cấp số hóa đơn thật ở Production. Không dùng để điều chỉnh hoặc thay thế hóa đơn đã phát hành. Gọi lại hóa đơn đã issued trả trạng thái đã phát hành, không phát hành lần nữa.
5. Tra cứu và đồng bộ trạng thái
Đọc dữ liệu đã lưu trên Pay2S, không gọi nhà cung cấp:
{
"action": "detail",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
Kết quả nằm trong data.invoice: ID, trạng thái, môi trường, mẫu/ký hiệu, số hóa đơn (invoice_number), người mua, items, subtotal, tax_total, grand_total, thời gian và provider_pdf_url/provider_xml_url khi nhà cung cấp có trả link. Có has_provider_reference để kiểm tra đã có mã nhà cung cấp chưa.
Yêu cầu đồng bộ từ nhà cung cấp:
{
"action": "sync",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
Sau sync, gọi detail để lấy bản cập nhật. Một số nhà cung cấp cần đủ mã tham chiếu trước khi đồng bộ được.
| Trạng thái | Cách xử lý |
|---|---|
draft |
Nháp; xem has_provider_reference để biết đã gửi nhà cung cấp chưa |
processing |
Đang xử lý, đợi và tra cứu |
issued |
Đã phát hành |
failed |
Xem lỗi và xử lý trên Dashboard; không tự tạo đơn mới để thử lại |
unknown |
Kết quả chưa rõ, đồng bộ/đối soát trước khi thử lại |
Nếu timeout ở bước gửi nháp/phát hành, tra cứu trước. Không tạo hóa đơn mới chỉ vì client không nhận được phản hồi. API chặn gửi/phát hành lại khi processing hoặc unknown. Phiên bản này dùng polling, chưa có webhook riêng cho trạng thái HĐĐT.
6. Lấy PDF/XML
{
"action": "files",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245,
"file_type": "all"
}
file_type: all (mặc định), pdf, xml. Kết quả JSON có data.pdf_base64, data.xml_base64; giải mã Base64 thành tệp. Trường có thể rỗng nếu nhà cung cấp không hỗ trợ. Một số nhà cung cấp chỉ cho tải sau khi phát hành. API không luôn trả một URL tải công khai.
Mã HTTP
| Mã | Ý nghĩa |
|---|---|
| 200 / 201 | Thành công / tạo nháp mới |
| 400 / 413 / 415 | JSON sai / quá lớn / sai Content-Type |
| 401 | Sai khóa, chữ ký, timestamp hoặc tài khoản bị khóa |
| 403 | Sai loại tài khoản, không đủ gói/quyền hoặc demo |
| 404 | Không có hóa đơn/kết nối thuộc tài khoản này |
| 405 | Không phải POST |
| 409 | Mã đơn trùng dữ liệu khác, đang xử lý hoặc cần đối soát |
| 422 | Dữ liệu không hợp lệ, chưa cấu hình mẫu hoặc nhà cung cấp từ chối |
| 429 | Quá 60 request/phút |
| 500 / 502 / 503 | Lỗi hệ thống/nhà cung cấp hoặc chưa sẵn sàng; giữ mã đơn, kiểm tra trạng thái trước khi thử lại |
Luôn kiểm tra cả HTTP và status trong JSON. Khi có error_reference, gửi mã này cho hỗ trợ; không gửi Secret Key.
Tự động gửi nháp hoặc phát hành khi tạo
Với action: "create_draft", chọn một trong ba cách:
auto_submit |
auto_publish |
Kết quả |
|---|---|---|
false hoặc bỏ qua |
false hoặc bỏ qua |
Chỉ tạo nháp tại Pay2S |
true |
false hoặc bỏ qua |
Tạo nháp tại Pay2S và gửi nháp sang nhà cung cấp |
false hoặc bỏ qua |
true |
Tạo và phát hành hóa đơn |
Hai trường phải là boolean JSON, không phải chuỗi. Không bật cả hai cùng lúc; API trả 422.
Playground chỉ dùng kết nối sandbox. Production vẫn cần quyền và confirm_production: true theo quy định API.
Nếu bước gửi nhà cung cấp lỗi sau khi đã lưu nháp, response có thể trả status: false nhưng vẫn có data.id, data.external_ref, data.local_draft_saved: true cùng cờ đã chọn. Giữ ID để tra cứu/đồng bộ trước khi thử lại; không tạo mã mới để tránh hóa đơn trù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.
Kết nối HĐĐT
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "connections"
}
HTTP 200. Response:
{
"status": true,
"data": {
"connections": [
{
"id": 14,
"name": "Mắt Bão eInvoice dùng thử",
"provider": "matbao_hddt",
"environment": "sandbox"
},
{
"id": 25,
"name": "MISA meInvoice dùng thử",
"provider": "misa_meinvoice",
"environment": "sandbox"
}
]
}
}
Tạo hóa đơn nháp
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "create_draft",
"connection_id": 14,
"environment": "sandbox",
"external_ref": "DEV_d33eb1382b55f87313b6840bff80f880_17916599753206049db",
"auto_submit": false,
"auto_publish": false,
"buyer_contact_name": "Bán cho người tiêu dùng",
"payment_method": "TM/CK",
"items": [
{
"code": "DEMO01",
"name": "Sản phẩm thử",
"unit": "Cái",
"quantity": 2,
"unit_price": 100000,
"tax_rate": 10
}
]
}
HTTP 201. Response:
{
"status": true,
"message": "Đã tạo bản nháp Sandbox.",
"data": {
"id": 2245,
"status": "draft",
"external_ref": "DEV_d33eb1382b55f87313b6840bff80f880_17916599753206049db"
}
}
Gửi nháp nhà cung cấp
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "submit_draft",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
HTTP 200. Response:
{
"status": true,
"message": "Đã tạo hóa đơn nháp trên Mắt Bão (Sandbox).",
"data": {
"id": 2245,
"status": "draft",
"provider_draft": true,
"invoice_number": "0",
"pdf_url": "https://beta-portalv2.mifi.vn/DownloadPDFCA.aspx?kk=1434747710&keyinv=mfMmRZZ2JBeERVSVE9&coid=MWNuWGNKQkk0MjA9&p=1&c=0&publishDomain=https://demo.matbao.in",
"xml_url": ""
}
}
Phát hành hóa đơn sandbox
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "publish",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
HTTP 200. Response:
{
"status": true,
"message": "Đã phát hành hóa đơn Sandbox.",
"data": {
"id": 2245,
"status": "issued",
"invoice_number": "53",
"email": null
}
}
Chi tiết hóa đơn
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "detail",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
HTTP 200. Response:
{
"status": true,
"message": "Success",
"data": {
"invoice": {
"id": 2245,
"store_id": 0,
"connection_id": 14,
"template_id": 5786,
"environment": "sandbox",
"source": "manual",
"status": "issued",
"khmshdon": "1",
"khhdon": "C26TYV",
"lookup_code": "CUS-4444-20261011021936-003FE7",
"reference_code": "P2S261011021936ECF9",
"invoice_number": "53",
"provider_pdf_url": "https://beta-portalv2.mifi.vn/DownloadPDFCA.aspx?kk=1434747710&keyinv=mfMmRZZ2JBeERVSVE9&coid=MWNuWGNKQkk0MjA9&p=1&c=0&publishDomain=https://demo.matbao.in",
"provider_xml_url": "",
"provider_status_code": null,
"provider_status_name": null,
"provider_synced_at": null,
"provider_sync_error": null,
"email_sent_at": null,
"email_last_error": null,
"email_attempt_count": 0,
"email_last_attempt_at": null,
"buyer_name": "",
"buyer_tax_code": "",
"buyer_citizen_id": "",
"buyer_address": "",
"buyer_phone": "",
"buyer_email": "",
"buyer_contact_name": "Bán cho người tiêu dùng",
"payment_method": "TM/CK",
"note": "",
"subtotal": 200000,
"tax_total": 20000,
"vat_reduction_total": 0,
"grand_total": 220000,
"error_message": null,
"attempt_count": 2,
"issued_at": "2026-10-11 02:19:37",
"created_at": "2026-10-11 02:19:36",
"updated_at": "2026-10-11 02:19:37",
"store_name": "",
"provider_draft_scope": "provider",
"has_provider_reference": true,
"pay2s_invoice_code": null,
"order_code": null,
"items": [
{
"id": 2306,
"position": 1,
"code": "DEMO01",
"name": "Sản phẩm thử",
"integration_source": "any",
"external_item_code": "",
"external_group_code": "",
"external_item_id": "",
"unit": "Cái",
"quantity": 2,
"unit_price": 100000,
"amount": 200000,
"tax_rate": 10,
"tax_amount": 20000,
"direct_vat_rate": null,
"vat_reduction_eligible": false,
"vat_reduction_rate": 0,
"vat_reduction_amount": 0,
"total": 220000
}
]
}
}
}
Đồng bộ hóa đơn
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "sync",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245
}
HTTP 200. Response:
{
"status": true,
"message": "Đã đồng bộ trạng thái hóa đơn.",
"data": {
"id": 2245,
"status": "issued",
"invoice_number": "53",
"provider_status_code": 3,
"provider_status_name": "Chưa gửi CQT",
"provider_synced_at": "2026-10-11 02:19:38"
}
}
Tải XML / PDF
Đã gọi API demo/UAT.
POST /api/v1/einvoices
Body:
{
"action": "files",
"connection_id": 14,
"environment": "sandbox",
"invoice_id": 2245,
"file_type": "all"
}
HTTP 200. Response:
{
"status": true,
"message": "Đã tải dữ liệu hóa đơn từ Mắt Bão.",
"data": {
"pdf_base64": "<PDF_BASE64_OMITTED>",
"xml_base64": "<XML_BASE64_OMITTED>"
}
}