Developer
Thử API trong Playground ↗

API hóa đơn điện tử

✧ Mở bằng AI

Sao chép tài liệu, mở AI rồi dán vào cuộc trò chuyện.

ChatGPTClaudePerplexityGrok
≡ Xem Markdown

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ị

  1. Tài khoản chính có gói Basic trở lên và được bật tính năng HĐĐT.
  2. 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 đó.
  3. 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.
  4. 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

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>"
  }
}
Tìm trong 57 trang tài liệu · Esc để đóng