Chuyển tới nội dung chính

Hóa đơn điện tử

Hạ Tầng phát hành hóa đơn điện tử theo mã số thuế của bạn, qua tài khoản riêng của bạn tại nhà cung cấp hóa đơn. Thêm khối invoice vào payment và hóa đơn được tạo khi đơn đã thanh toán; hoặc tự tạo hóa đơn cho bất kỳ payment hay giao dịch ngân hàng nào. Trang này hướng dẫn kết nối nhà cung cấp, vòng đời hóa đơn, xử lý lỗi, và đối soát tiền vào với hóa đơn.

Trước khi bắt đầu​

  • Tài khoản hóa đơn điện tử Mắt Bão có quyền dùng API. Mã số thuế, tài khoản và mật khẩu API nằm trong mục "Cấu hình API" trên cổng Mắt Bão.
  • Ít nhất một mẫu số và ký hiệu đã đăng ký trên tài khoản đó.
  • API key có payments:write. Hóa đơn điện tử dùng chung scope và môi trường với Thanh toán.

Ở development, hãy kết nối tài khoản sandbox demo của Mắt Bão: hóa đơn là hóa đơn sandbox thật nhưng không bao giờ gửi tới cơ quan thuế.

1. Kết nối nhà cung cấp​

Làm một lần cho mỗi môi trường, trên dashboard (Cài đặt thanh toán → Nhà cung cấp hoá đơn) hoặc qua API. Trước tiên kiểm tra thông tin đăng nhập và liệt kê mẫu hóa đơn của tài khoản; bước này không lưu gì:

curl https://api.hatang.dev/einvoices/connections/check \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "provider": "matbao", "tax_code": "0109876543", "username": "api-user", "password": "..." }'
{
"object": "einvoice_connection_check",
"ok": true,
"provider": "matbao",
"seller_tax_code": "0109876543",
"seller_name": "CONG TY TNHH VI DU",
"templates": [
{ "pattern": "1", "serial": "C26TAT", "name": "Hóa đơn giá trị gia tăng", "remaining": 4980 }
]
}

Sau đó kết nối với mẫu bạn chọn. Body gồm:

TrườngKiểuBắt buộcMặc địnhÝ nghĩa
providerchuỗiCó–matbao (GET /einvoices/providers liệt kê các nhà cung cấp)
tax_code, username, passwordchuỗiCó với matbao–Mã số thuế người bán và tài khoản API của nhà cung cấp; được lưu mã hóa
patternchuỗi, 1 đến 6Có–Mẫu số mặc định cho hóa đơn mới; phải là một mẫu của tài khoản
serialchuỗi, đúng 6Có–Ký hiệu mặc định, ví dụ C26TAT
default_modedraft | publishKhôngdraftKhi đơn đã thanh toán yêu cầu hóa đơn mà không có mode: draft dừng ở nháp, publish gửi ký luôn
is_defaultbooleanKhôngtrue với nhà cung cấp đầu tiênHóa đơn mới dùng nhà cung cấp mặc định
import_externalbooleanKhôngfalseNhập cả hóa đơn tạo trực tiếp trên trang nhà cung cấp
labelchuỗi, tối đa 100Không–Tên bạn đặt cho kết nối

Schema OpenAPI: POST /einvoices/connections

curl https://api.hatang.dev/einvoices/connections \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "provider": "matbao", "tax_code": "0109876543", "username": "api-user", "password": "...",
"pattern": "1", "serial": "C26TAT", "default_mode": "publish" }'

Phản hồi (201) là kết nối, kèm callback_url. Gửi URL này cho bộ phận hỗ trợ Mắt Bão để thay đổi phía họ về Hạ Tầng ngay; nếu không, Hạ Tầng kiểm tra lại các hóa đơn còn đang chờ sau 1 giờ kể từ khi tạo, 15 phút một lần, trong tối đa 7 ngày. Bản nháp bạn cố ý để lại (mode: "draft") không được kiểm tra: gọi POST /einvoices/{id}/sync để lấy các chỉnh sửa trên trang nhà cung cấp. Thông tin đăng nhập được lưu mã hóa. Nếu nhà cung cấp bắt đầu từ chối thông tin này, kết nối chuyển sang needs_reauth và hóa đơn tạm dừng cho tới khi bạn lưu thông tin mới bằng PATCH /einvoices/connections/{id}.

EndpointChức năng
GET /einvoices/providersCác nhà cung cấp kết nối được, kèm các trường mỗi bên cần
GET /einvoices/connections / GET /einvoices/connections/{id}Các kết nối của bạn
PATCH /einvoices/connections/{id}Đổi chế độ mặc định, mẫu, thông tin đăng nhập, nhãn
POST /einvoices/connections/{id}/defaultĐặt làm nhà cung cấp mặc định
GET /einvoices/connections/{id}/templatesCác mẫu tài khoản được phép phát hành, đọc trực tiếp
POST /einvoices/connections/{id}/syncLấy thay đổi từ nhà cung cấp ngay
DELETE /einvoices/connections/{id}Gỡ liên kết. Hóa đơn vẫn còn; nhà cung cấp tiếp theo trở thành mặc định

2. Thêm hóa đơn vào payment​

Thêm khối invoice khi tạo payment. Không có gì được gửi tới nhà cung cấp cho tới khi payment ở trạng thái paid hoặc overpaid.

curl https://api.hatang.dev/payments \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Idempotency-Key: order-1002" \
-H "Content-Type: application/json" \
-d '{
"amount": 275000,
"description": "Đơn hàng #1002",
"ipn_url": "https://shop.example.vn/hatang/ipn",
"metadata": { "order_id": "1002" },
"invoice": {
"mode": "publish",
"template": { "pattern": "1", "serial": "C26TAT" },
"buyer": {
"name": "Nguyễn Văn Khách",
"legal_name": "Công ty TNHH Khách Hàng",
"tax_code": "0109876543",
"address": "1 Tràng Tiền, Hà Nội",
"email": "[email protected]"
},
"items": [{ "name": "Gói dịch vụ tháng 9", "unit": "gói", "quantity": 1, "unit_price": 250000, "vat_rate": 10 }],
"payment_method": "CK",
"send_email": true
}
}'
TrườngKiểuBắt buộcMặc địnhÝ nghĩa
modedraft | publishKhôngdefault_mode của kết nốidraft dừng ở bản nháp, bạn xem lại rồi gọi POST /einvoices/{id}/publish; publish tạo nháp rồi gửi ký luôn
templateobject { pattern, serial }Khôngmẫu của kết nốipattern: mẫu số, 1 đến 6 ký tự; serial: ký hiệu, đúng 6 ký tự
buyerngười muaCó–Cần name hoặc legal_name
itemsmảng dòng hàng, 1 đến 200Có–Các dòng hàng trên hóa đơn
payment_methodchuỗi, tối đa 50KhôngTM/CKHình thức thanh toán, in trên hóa đơn
notechuỗi, tối đa 500Không–Ghi chú, in trên hóa đơn
send_emailbooleanKhôngfalseGửi email hóa đơn đã phát hành cho người mua (tới buyer.email)

Schema OpenAPI: POST /payments, POST /einvoices

Tổng tiền

Hãy để tổng hóa đơn (sau VAT) bằng amount của payment: ở ví dụ trên 250.000 + 10% = 275.000. Hạ Tầng không kiểm tra điều này. Mỗi dòng được làm tròn tới đồng riêng; khi giá của bạn đã gồm VAT, hãy gửi vat_amount cho từng dòng để tổng khớp tới từng đồng. Xem Tổng tiền và làm tròn VAT.

3. Sau khi thanh toán​

Mỗi hóa đơn đi qua cùng hai bước, tạo nháp và gửi ký; mỗi bước được xếp hàng và tự thử lại độc lập:

invoice.statusÝ nghĩa
waiting_for_paymentChưa trả; chưa gửi gì (chỉ có trên payment)
creatingĐã trả; đang tạo nháp tại nhà cung cấp
draftĐã có nháp. Dừng ở đây nếu mode là draft
publishingĐã gửi ký
issuedĐã ký: có number và lookup_code; chờ cơ quan thuế
tax_accepted / tax_rejectedPhản hồi của cơ quan thuế (tax_authority_code khi chấp nhận)
failedNhà cung cấp từ chối một bước: error cho biết lý do, failed_step cho biết bước nào
canceled / replaced / adjustedThay đổi sau đó, trên trang nhà cung cấp hoặc bằng hóa đơn thay thế
deletedNháp đã bị xóa

Trường invoice của payment (các trường) luôn phản ánh trạng thái hiện tại của hóa đơn, và mọi thay đổi được gửi qua IPN và webhook invoice.*:

"invoice": {
"requested": true,
"mode": "publish",
"status": "issued",
"id": "einv_01a0b90429f678e68ed7de882720a2b4",
"number": "8003",
"series": "1C26TAT",
"template": { "pattern": "1", "serial": "C26TAT" },
"lookup_code": "HTXAEM9P3AYXSM",
"tax_authority_code": null,
"pdf_url": "https://…",
"sync_state": "in_sync",
"issued_at": "2026-09-18T17:00:00.000Z",
"error": null,
"failed_step": null
}

Nếu payment được trả khi chưa kết nối nhà cung cấp nào ở môi trường đó, invoice.status là failed với id: null và lỗi "No e-invoice provider is connected in this environment".

Trang checkout chỉ cho người mua biết có hóa đơn hay không, và khi đã phát hành thì số hóa đơn và mã tra cứu.

Xem lại nháp trước khi ký​

Với mode: "draft", hóa đơn dừng ở draft. Khi đó bạn có thể:

Lời gọiChức năng
PATCH /einvoices/{id}Sửa buyer, items, payment_method, note
POST /einvoices/{id}/publishGửi ký
DELETE /einvoices/{id}Xóa nháp

Chỉ nháp mới sửa hoặc gửi ký được; trạng thái khác trả về 409 invalid_state. Ngay cả mode: "publish" cũng không phát hành trong một lời gọi: gửi ký luôn là một bước riêng, được xếp hàng.

Khi một bước lỗi​

Timeout và sự cố của nhà cung cấp được thử lại tự động. Khi bị từ chối (ví dụ mã số thuế người mua không hợp lệ), hóa đơn dừng ở failed kèm thông báo của nhà cung cấp:

  1. Sửa theo thông báo. Nếu lỗi ở bước tạo nháp (failed_step: "create_draft"), sửa người mua hoặc dòng hàng bằng PATCH /einvoices/{id}.
  2. POST /einvoices/{id}/retry chạy lại đúng bước đó.
  3. GET /einvoices/{id}/history liệt kê mọi lần đổi trạng thái, cũ nhất trước, kèm phía thực hiện (source) và thời điểm.
{
"object": "list",
"data": [
{ "from": null, "to": "creating", "source": "hatang", "detail": { "mode": "publish", "template": "1/C26TAT" }, "at": "2026-09-18T16:59:58.120Z" },
{ "from": "draft", "to": "publishing", "source": "hatang", "detail": null, "at": "2026-09-18T16:59:59.010Z" }
]
}

Hóa đơn không gắn payment​

POST /einvoices nhận cùng các trường như khối invoice, thêm:

TrườngKiểuBắt buộcMặc địnhÝ nghĩa
payment_idchuỗi pay_…Không–Gắn với một payment. Mỗi payment có tối đa một hóa đơn; hóa đơn thứ hai trả về 409 invalid_state
transaction_idchuỗi txn_…Không–Gắn với một giao dịch ngân hàng nhận không qua đơn
connection_idchuỗi icn_…Khôngkết nối mặc địnhKết nối nhà cung cấp phát hành hóa đơn

Schema OpenAPI: POST /einvoices

curl https://api.hatang.dev/einvoices \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "publish",
"transaction_id": "txn_123",
"buyer": { "name": "Nguyễn Văn Khách" },
"items": [{ "name": "Cà phê hạt 1 kg", "quantity": 1, "unit_price": 91667, "vat_rate": 8 }]
}'

Phản hồi (201) là object hóa đơn (object: "einvoice") ở trạng thái creating, kèm buyer, items và totals đã tính (subtotal, vat, total). Theo dõi bằng GET /einvoices/{id} hoặc webhook invoice.*. GET /einvoices liệt kê hóa đơn, lọc theo status, payment_id hoặc sync_state.

Thay thế, hủy và xung đột​

  • Thay thế. Hóa đơn đã gửi cơ quan thuế (issued, tax_accepted, tax_rejected) không thể hủy hay xóa qua Hạ Tầng. POST /einvoices/{id}/replace kèm reason, buyer và items đã sửa sẽ tạo hóa đơn thay thế: một bản nháp ghi rõ hóa đơn gốc, rồi gửi ký. Hóa đơn gốc chuyển sang replaced khi nhà cung cấp đã có hóa đơn thay thế.
  • Xóa nháp (hoặc hóa đơn lỗi) bằng DELETE /einvoices/{id}.
  • Thay đổi trên trang nhà cung cấp được đồng bộ về Hạ Tầng: ký, mã của cơ quan thuế, hủy, thay thế, sửa và xóa nháp. Sự kiện của chúng có source: "provider". Hóa đơn tạo trực tiếp trên trang nhà cung cấp chỉ được nhập khi kết nối bật import_external: true.
  • Xung đột. Nháp bị sửa ở cả Hạ Tầng và trang nhà cung cấp sẽ có sync_state: "conflict" và sự kiện invoice.conflict. Giải quyết bằng POST /einvoices/{id}/resolve với { "keep": "hatang" } hoặc { "keep": "provider" }. Trước khi giải quyết, không sửa hay gửi ký được.
  • Kiểm tra ngay. POST /einvoices/{id}/sync hỏi nhà cung cấp về một hóa đơn ngay lập tức.

Đối soát tiền vào với hóa đơn​

Mọi giao dịch tiền vào tài khoản đều nên có hóa đơn, trừ khi không cần. Giao dịch được tính là đã có hóa đơn khi một hóa đơn còn hiệu lực (không lỗi, không bị xóa, hủy hay bị cơ quan thuế từ chối) trỏ tới nó, trực tiếp hoặc qua payment mà nó đã trả. Ngược lại là thiếu hóa đơn, trừ khi bạn đánh dấu không cần hóa đơn.

curl "https://api.hatang.dev/einvoices/reconciliation?from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $HATANG_API_KEY"

from và to là ngày theo giờ Việt Nam (YYYY-MM-DD), cách nhau tối đa 400 ngày; mặc định là 30 ngày gần nhất. Phản hồi có totals (money_in, invoiced, missing, exempt, mỗi mục có count và amount), một dòng cho mỗi ngày, cùng danh sách giao dịch missing và exempt. Dashboard hiển thị cùng báo cáo tại Thanh toán → Đối soát hoá đơn.

Đánh dấu giao dịch không cần hóa đơn (hoàn tiền, chuyển giữa các tài khoản của chính bạn), hoặc bỏ đánh dấu:

curl https://api.hatang.dev/payments/transactions/txn_123/invoice-exempt \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Chuyển giữa các tài khoản của công ty" }'

curl -X DELETE https://api.hatang.dev/payments/transactions/txn_123/invoice-exempt \
-H "Authorization: Bearer $HATANG_API_KEY"

Chủ sở hữu và quản trị viên của doanh nghiệp có nhà cung cấp đang hoạt động còn nhận email hằng ngày lúc 08:00 (giờ Việt Nam) liệt kê tiền vào hôm trước còn thiếu hóa đơn, và email khi một hóa đơn bị lỗi, bị cơ quan thuế từ chối, bị xung đột, hoặc sau 1 giờ kể từ khi tạo vẫn chưa tới cơ quan thuế. Bản nháp bạn cố ý để lại (mode: "draft") không bị báo.

Các object và cách chúng liên kết​

Khi một đơn hàng được thanh toán và xuất hóa đơn, có ba object tham gia: payment bạn tạo, transaction ngân hàng báo về, và hóa đơn điện tử (einvoice). Mỗi object trỏ tới các object kia bằng id, nên bạn bắt đầu từ object nào mà sự kiện gửi tới cũng được.

Lưu vào đơn hàngLấy từ đâuDùng để
id của payment (pay_…)Phản hồi của POST /paymentsGET /payments/{id}; là data.id trong mọi lần gọi IPN
id của hóa đơn (einv_…)invoice.id của payment khi hóa đơn đã có (null trước khi payment được trả), hoặc phản hồi của POST /einvoicesGET, PATCH, publish, retry, replace trên /einvoices/{id}; là data.id trong webhook invoice.*
number, series, lookup_codeHóa đơn, khi đã issuedCho người mua biết hóa đơn của họ và nơi tra cứu
referenceHóa đơn, ngay từ đầuMã tham chiếu Hạ Tầng dùng ở nhà cung cấp; tìm hóa đơn trên trang của nhà cung cấp bằng mã này
  • Qua payment. Thêm khối invoice vào POST /payments, hoặc gọi POST /einvoices kèm payment_id. Mỗi payment có một hóa đơn đang hiệu lực; hóa đơn thay thế giữ nguyên payment_id, và trường invoice của payment luôn là hóa đơn đang hiệu lực. GET /einvoices?payment_id=pay_… liệt kê tất cả.
  • Qua giao dịch, không có payment. Nếu bạn chỉ nhận webhook transaction.received, gọi POST /einvoices kèm transaction_id của giao dịch đó.
  • Sự kiện. Lần gọi IPN mang payment, trạng thái hóa đơn nằm ở data.invoice. Webhook invoice.* mang nguyên object hóa đơn, kèm source (hatang, hoặc provider khi thay đổi đến từ trang của nhà cung cấp).

Yêu cầu xuất hóa đơn​

Cùng một object, cùng quy tắc, được gửi ở bốn chỗ:

Ở đâuNhận gì
invoice trong POST /paymentsMọi trường bên dưới
POST /einvoicesMọi trường bên dưới, thêm payment_id, transaction_id, connection_id
PATCH /einvoices/{id} (bản nháp)buyer, items, payment_method, note; giá trị gửi lên thay hẳn giá trị cũ; đều không bắt buộc, chỉ gửi trường cần đổi
POST /einvoices/{id}/replacereason, buyer, items, payment_method, note

Một yêu cầu đầy đủ: người mua là công ty, hai dòng hàng (một dòng tự nhập tiền thuế, một dòng không chịu thuế):

{
"mode": "publish",
"template": { "pattern": "1", "serial": "C26TAT" },
"buyer": {
"name": "Nguyen Van Khach",
"legal_name": "Cong ty TNHH Khach Hang",
"tax_code": "0109876543",
"address": "1 Trang Tien, Ha Noi",
"email": "[email protected]",
"phone": "0901234567",
"buyer_code": "KH-0042"
},
"items": [
{ "name": "Coffee 1 kg", "code": "CF-1KG", "unit": "kg", "quantity": 1, "unit_price": 45455, "vat_rate": 10, "vat_amount": 4545 },
{ "name": "Delivery", "unit": "order", "quantity": 1, "unit_price": 20000, "vat_rate": -1 }
],
"payment_method": "CK",
"note": "Order #1002",
"send_email": true
}

Tổng của nó: tiền hàng 65.455, VAT 4.545, tổng cộng 70.000.

TrườngKiểuBắt buộcMặc địnhÝ nghĩa
modedraft | publishKhôngdefault_mode của kết nốidraft dừng ở bản nháp, bạn xem lại rồi gọi POST /einvoices/{id}/publish; publish tạo nháp rồi gửi ký luôn
templateobject { pattern, serial }Khôngmẫu của kết nốipattern: mẫu số, 1 đến 6 ký tự; serial: ký hiệu, đúng 6 ký tự
buyerngười muaCó–Cần name hoặc legal_name
itemsmảng dòng hàng, 1 đến 200Có–Các dòng hàng trên hóa đơn
payment_methodchuỗi, tối đa 50KhôngTM/CKHình thức thanh toán, in trên hóa đơn
notechuỗi, tối đa 500Không–Ghi chú, in trên hóa đơn
send_emailbooleanKhôngfalseGửi email hóa đơn đã phát hành cho người mua (tới buyer.email)

Schema OpenAPI: POST /payments, POST /einvoices

Người mua​

Cần name, legal_name hoặc cả hai. Với công ty, legal_name là tên công ty và name là người mua hàng; với cá nhân, chỉ cần name.

TrườngKiểuBắt buộcMặc địnhÝ nghĩa
namechuỗi, tối đa 200Một trong hai–Người mua hàng, in ở ô "Họ tên người mua hàng"
legal_namechuỗi, tối đa 400Một trong hai–Tên công ty theo đăng ký, in ở ô "Tên đơn vị". Khách cá nhân để trống
tax_code10 đến 14 chữ số hoặc dấu gạchKhông–Mã số thuế người mua, ví dụ 0109876543 hoặc 0109876543-001
addresschuỗi, tối đa 400Không–Địa chỉ người mua
emailemailKhông–Nơi send_email gửi hóa đơn tới
phonechuỗi, tối đa 20Không–Số điện thoại người mua
buyer_codechuỗi, tối đa 50Không–Mã khách hàng của bạn, in trên hóa đơn

Schema OpenAPI: POST /payments, POST /einvoices

Dòng hàng​

TrườngKiểuBắt buộcMặc địnhÝ nghĩa
namechuỗi, 1 đến 500Có–Tên hàng hóa, dịch vụ
quantitysố lớn hơn 0Có–Được có số lẻ, ví dụ 1.5 kg
unit_pricesố, từ 0Có–Đơn giá trước VAT, tính bằng đồng. Được có số lẻ
vat_rate10 | 8 | 5 | 0 | -1 | -2Không10-1: không chịu thuế (KCT); -2: không kê khai, tính nộp thuế (KKKNT)
vat_amountsố đồng chẵn, từ 0Khôngtính từ vat_rateTiền thuế của dòng, dùng đúng như gửi. Chỉ dùng khi vat_rate lớn hơn 0. Xem Tổng tiền và làm tròn VAT
discountsố, từ 0Không0Chiết khấu cho cả dòng, tính bằng đồng trước VAT
unitchuỗi, tối đa 50Không–Đơn vị tính, ví dụ kg, gói
codechuỗi, tối đa 50Không–Mã hàng (SKU) của bạn

Schema OpenAPI: POST /payments, POST /einvoices

Trong object hóa đơn, mỗi dòng trả về những gì bạn đã gửi, thêm ba số tiền: amount (thành tiền trước VAT), vat_amount và total.

Tổng tiền và làm tròn VAT​

Đồng không có đơn vị nhỏ hơn, nên từng dòng được làm tròn riêng, rồi hóa đơn cộng các dòng đã làm tròn. Nhà cung cấp in đúng các số này.

Số tiềnCách tính
amount của dòngquantity × unit_price − discount, làm tròn tới đồng
vat_amount của dòngvat_amount nếu bạn gửi; nếu không thì amount × vat_rate / 100, làm tròn; bằng 0 khi vat_rate là 0, -1 hoặc -2
total của dòngamount + vat_amount
totals.subtotal / totals.vat / totals.totalTổng amount / vat_amount / total của các dòng

Số lẻ 0,5 làm tròn lên. Hạ Tầng không so tổng hóa đơn với amount của payment: bạn cần tự để hai số này bằng nhau.

Khi giá đã gồm VAT. Cửa hàng thường biết số tiền người mua trả (đã gồm VAT) rồi tính ngược ra giá trước thuế. Làm tròn giá đó rồi làm tròn tiền thuế có thể lệch một đồng. Ví dụ 50.000 đồng, thuế 10%:

unit_pricevat_amounttotal
Hạ Tầng tự tính VAT45.4554.546 (45.455 × 10%, làm tròn)50.001
Gửi kèm vat_amount45.4554.54550.000

Vì vậy, với mỗi dòng, gửi vat_amount bằng phần còn lại của số tiền người mua trả sau khi đã làm tròn giá trước thuế:

// gross: số tiền người mua trả cho cả dòng, đã gồm VAT, tính bằng đồng.
function lineFromGross(name: string, quantity: number, gross: number, vatRate: 10 | 8 | 5) {
const unit_price = Math.round(gross / quantity / (1 + vatRate / 100));
const amount = Math.round(quantity * unit_price);
return { name, quantity, unit_price, vat_rate: vatRate, vat_amount: gross - amount };
}

lineFromGross("Coffee 1 kg", 1, 50_000, 10);
// { name: "Coffee 1 kg", quantity: 1, unit_price: 45455, vat_rate: 10, vat_amount: 4545 }

Quy tắc của vat_amount:

  • Số đồng chẵn, từ 0. 4545.5 bị từ chối với 422 validation_error; Hạ Tầng không tự làm tròn.
  • Chỉ dùng cho dòng có vat_rate lớn hơn 0; dòng 0, -1 hoặc -2 gửi kèm sẽ bị từ chối với 422 validation_error.
  • Hạ Tầng không đối chiếu với vat_rate; chỉ nên lệch đúng phần làm tròn bạn cần.
  • Số này được gửi sang nhà cung cấp làm tiền thuế của dòng và được giữ nguyên khi đọc hóa đơn về, nên đồng bộ sau đó hay sửa trên trang nhà cung cấp đều không biến nó thành xung đột.
  • Trên dashboard, ô Tiền thuế VAT trong Hoá đơn mới nhận cùng giá trị, viết 4545, 4.545 hay 4,545 đều được.

Object hóa đơn​

Là phản hồi của POST /einvoices và GET /einvoices/{id}, và là data của mọi webhook invoice.*. Hóa đơn từ yêu cầu ở trên (chỉ dòng đầu), đã thanh toán và phát hành:

{
"object": "einvoice",
"id": "einv_01a178887ba6771caef28863e6f55879",
"environment": "production",
"connection_id": "icn_01a178887b357a9c8bc4ad098f6b8855",
"provider": "matbao",
"payment_id": "pay_01a178887b59783cb5f4f18f06071ed1",
"transaction_id": null,
"origin": "hatang",
"mode": "publish",
"status": "issued",
"sync_state": "in_sync",
"version": 1,
"buyer": {
"name": "Nguyen Van Khach",
"legal_name": "Cong ty TNHH Khach Hang",
"tax_code": "0109876543",
"address": "1 Trang Tien, Ha Noi",
"email": "[email protected]"
},
"items": [
{ "name": "Coffee 1 kg", "unit": "kg", "quantity": 1, "unit_price": 45455, "vat_rate": 10, "amount": 45455, "vat_amount": 4545, "total": 50000 }
],
"totals": { "subtotal": 45455, "vat": 4545, "total": 50000 },
"currency": "VND",
"payment_method": "CK",
"note": null,
"template": { "pattern": "1", "serial": "C26TAT" },
"reference": "HT45MSKX4UY3AA",
"provider_invoice_id": "4/AUx/syzogcoBsIReMUk6…",
"number": "8001",
"series": "1C26TAT",
"lookup_code": "HT45MSKX4UY3AA",
"tax_authority_code": null,
"pdf_url": "https://…",
"issued_at": "2026-10-25T17:00:00.000Z",
"related_invoice_id": null,
"relation": null,
"conflict": null,
"error": null,
"failed_step": null,
"created_at": "2026-10-26T14:06:12.895Z",
"updated_at": "2026-10-26T14:06:13.004Z"
}
TrườngKiểuCó thể nullÝ nghĩa
objectchuỗiKhôngLuôn là einvoice
idchuỗiKhôngeinv_…
environmentdevelopment | productionKhôngMôi trường của API key đã tạo hóa đơn
connection_idchuỗiKhôngKết nối nhà cung cấp phát hành hóa đơn (icn_…)
providerchuỗiCóNhà cung cấp, ví dụ matbao
payment_idchuỗiCóPayment của hóa đơn
transaction_idchuỗiCóGiao dịch ngân hàng của hóa đơn, khi không có payment
originhatang | providerKhônghatang khi tạo qua Hạ Tầng; provider khi được đưa về từ trang nhà cung cấp (import_external)
modedraft | publishKhôngNhư đã yêu cầu
statuschuỗiKhôngTrạng thái hiện tại: xem Sau khi thanh toán
sync_statein_sync | pending_push | conflictKhôngpending_push khi một thay đổi đang được gửi sang nhà cung cấp; conflict khi bản nháp bị sửa ở cả hai phía
versionsố nguyênKhôngTăng một sau mỗi thay đổi qua Hạ Tầng
buyerngười muaKhôngNhư đã gửi; chỉ có các trường bạn đã gửi
itemsmảng dòng hàngKhôngNhư đã gửi, mỗi dòng kèm amount, vat_amount và total đã tính
totalsobject { subtotal, vat, total }KhôngTính bằng đồng
currencychuỗiKhôngVND
payment_method, notechuỗiCóNhư đã gửi
templateobject { pattern, serial }KhôngMẫu số và ký hiệu dùng để phát hành
referencechuỗi, tối đa 20KhôngMã tham chiếu Hạ Tầng dùng ở nhà cung cấp
provider_invoice_idchuỗiCóId của chính nhà cung cấp, khi bản nháp đã có
numberchuỗiCóSố hóa đơn, khi đã issued
serieschuỗiCóMẫu số và ký hiệu ghép lại, ví dụ 1C26TAT
lookup_codechuỗiCóMã người mua nhập ở trang tra cứu của nhà cung cấp
tax_authority_codechuỗiCóMã của cơ quan thuế, khi đã tax_accepted
pdf_urlURLCóFile PDF của nhà cung cấp, khi đã có
issued_atchuỗi ISO 8601CóNgày ghi trên hóa đơn
related_invoice_idchuỗiCóỞ hóa đơn thay thế: hóa đơn bị thay
relationreplaces | adjustsCóỞ hóa đơn thay thế (replaces) hoặc điều chỉnh (adjusts)
conflictobjectCóKhi sync_state: "conflict": bản của nhà cung cấp, nằm trong content
errorchuỗiCóLý do lỗi
failed_stepcreate_draft | update_draft | publish | delete_draftCóBước bị lỗi
created_at, updated_atchuỗi ISO 8601Không

Schema OpenAPI: GET /einvoices/{id}

Tóm tắt hóa đơn trên payment​

Object payment giữ một bản tóm tắt hóa đơn của nó trong invoice, để handler IPN thấy hóa đơn mà không phải gọi thêm. Trường này là null khi không yêu cầu xuất hóa đơn và cũng chưa có hóa đơn nào.

TrườngKiểuCó thể nullÝ nghĩa
requestedbooleanKhôngtrue khi payment được tạo kèm khối invoice
modedraft | publishCóNhư đã yêu cầu
statuschuỗiKhôngNhư trên hóa đơn, thêm waiting_for_payment khi payment chưa được trả. failed với id: null khi đã trả mà chưa kết nối nhà cung cấp
idchuỗiCóId einv_… của hóa đơn, null cho tới khi hóa đơn được tạo
number, series, template, lookup_code, tax_authority_code, pdf_url, sync_state, issued_at, error, failed_stepnhư trên hóa đơnCóNhư trên hóa đơn; null cho tới khi có hóa đơn

Schema OpenAPI: GET /payments/{id}

Để đọc các dòng hàng và tổng tiền, lấy hóa đơn: GET /einvoices/{invoice.id}.

Lỗi​

LỗiKhi nào
422 validation_errorTrường sai (người mua không có tên, mã số thuế sai, quá 200 dòng), hoặc mẫu chưa đăng ký trên tài khoản
422 missing_required_fieldThiếu thông tin đăng nhập của nhà cung cấp
422 invoice_provider_errorNhà cung cấp từ chối request; thông báo nêu lý do
409 invalid_stateChưa kết nối nhà cung cấp, payment đã có hóa đơn, hóa đơn không ở trạng thái cho phép lời gọi này, hoặc cần giải quyết xung đột trước
403 environment_mismatchNhà cung cấp không có ở môi trường này
503 provider_unavailableNhà cung cấp không phản hồi; thử lại

Bước tiếp theo​

Xem trang này dạng Markdown