Nếu bạn là engineer vừa được thông báo “invoice quý sau phải là PDF/A-3 kèm Factur-X”, và toàn bộ bối cảnh chỉ là ai đó bên legal đã nói các từ đó, bài này dành cho bạn.
Chúng ta sẽ bỏ qua giọng văn của tài liệu tiêu chuẩn và giải thích các profile này thật sự ràng buộc gì, vì sao chính phủ bắt đầu bắt buộc chúng, và pipeline thực tế nhỏ nhất để emit một PDF compliant từ renderer dữ liệu có cấu trúc.
PDF/A trong hai đoạn
PDF là format linh hoạt. Quá linh hoạt: cùng một PDF spec cho phép embed JavaScript, link tới tài nguyên ngoài có thể không tồn tại sau 50 năm, mã hóa nội dung bằng cryptography đảo ngược được, tham chiếu font ngoài và hàng trăm thứ khác khiến tài liệu không còn self-contained.
PDF/A (“A” là Archival) là một profile của PDF, cấm các phần sẽ ngăn tài liệu render giống nhau sau 50 năm. Các quy tắc cấp cao:
- Tất cả font phải được embed.
- Không JavaScript, không external links, không audio/video.
- Không encryption.
- Transparency phải được flatten hoặc được profile version hỗ trợ.
- Màu phải device-independent, cần ICC profile.
- Mọi nội dung phải nằm trong file; không có tham chiếu phụ thuộc vào network.
Có nhiều version, mỗi version nới thêm cho các tính năng mới hơn:
| Profile | Năm | Thêm gì |
|---|---|---|
| PDF/A-1b | 2005 | Baseline ban đầu, nghiêm nhất |
| PDF/A-2b | 2011 | Cho phép JPEG2000, transparency, layers |
| PDF/A-3b | 2012 | Cho phép arbitrary file attachments, nền tảng của Factur-X |
| PDF/A-4 | 2020 | Dựa trên ISO 32000-2 (PDF 2.0), mô hình conformance đơn giản hơn |
Hậu tố “b” nghĩa là “basic” conformance, tức giữ fidelity thị giác. Còn có biến thể “u” (unicode-mapped) và “a” (accessibility-tagged). Với phần lớn workflow invoice/receipt, “b” là thứ bạn muốn, vì lưu trữ thuế quan tâm khả năng tái hiện thị giác, không phải screen-reader semantics.
Takeaway thực tế: nếu renderer nói hỗ trợ PDF/A-3b, nó nên là một config flag duy nhất ({ profile: "PDF/A-3b" } hoặc tương đương). Nếu bạn phải chạy công cụ thứ hai như Ghostscript, qpdf hoặc Acrobat để convert sau đó, hãy tính đó là khoảng trống workflow trong ops.
Vì sao PDF/A-3 quan trọng: nó là carrier cho e-invoice
PDF/A-3 thêm một khả năng đã trở nên cực kỳ quan trọng: file attachment tùy ý bên trong PDF.
Nghe có vẻ nhàm chán. Không phải vậy. Đây là nền tảng kỹ thuật cho các mandate e-invoice đang được triển khai trên khắp châu Âu.
Kiến trúc là một file PDF duy nhất vừa là:
- Invoice con người đọc được: layout thị giác, totals, branding.
- XML invoice máy đọc được: phần software của cơ quan thuế parse.
Cả hai nằm trong một file, cùng đại diện cho một invoice, và wrapper PDF/A-3 bảo đảm file vẫn parse được sau nhiều thập kỷ.
Hai format XML chính:
- Factur-X (Pháp): XML profile dựa trên UN/CEFACT Cross Industry Invoice
- ZUGFeRD (Đức): về bản chất tương đương Factur-X; hai tiêu chuẩn đã hợp nhất kỹ thuật từ 2018
- EN 16931: chuẩn châu Âu mà cả hai implementation conform
Với phần lớn workflow, “Factur-X” và “ZUGFeRD” có thể xem như thuật ngữ thay thế nhau: cùng schema, cùng cơ chế embedding, và một PDF compliant với một bên nhìn chung cũng compliant với bên kia.
Bắt buộc ở đâu, khi nào
Snapshot không đầy đủ cho engineer lên kế hoạch rollout Q2/Q3 2026:
| Quốc gia | Trạng thái | Format yêu cầu |
|---|---|---|
| Đức | Bắt buộc B2B từ 2025-01-01 cho nhận invoice; từ 2027 cũng cho phát hành | EN 16931 (Factur-X / ZUGFeRD / XRechnung) |
| Pháp | Bắt buộc phát hành cho doanh nghiệp lớn 2026-09; SMEs 2027-09 | Factur-X qua Chorus Pro |
| Ý | Bắt buộc B2B từ 2019 | FatturaPA qua SDI |
| Ba Lan | Bắt buộc từ 2024-07 | KSeF |
| Tây Ban Nha | Bắt buộc từ 2026 (B2B) | Facturae qua FACe |
| Bỉ | Bắt buộc từ 2026-01 | Peppol BIS 3 |
Pattern: mọi quốc gia thành viên EU đang triển khai một dạng e-invoicing conform EN 16931 trong giai đoạn 2024-2027. Nếu khách hàng của bạn hoạt động ở các thị trường đó, PDF generator của bạn sẽ cần emit XML đính kèm cùng invoice thị giác.
Pipeline thực dụng nhỏ nhất
Tạm quên tài liệu tiêu chuẩn viết gì. Nhìn từ engineering, pipeline là:
┌─────────────────────┐
│ Your invoice data │ (already a JSON object somewhere)
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Build EN 16931 XML │ (deterministic mapping; well-tested libs exist)
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Render PDF/A-3b + │
│ attach the XML │ (single API call to gPdf — or two-step elsewhere)
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Hand off to │
│ Chorus Pro / SDI / │
│ Peppol / etc │
└─────────────────────┘
Hai bước không tầm thường:
Bước 1: build XML
Việc này khó chịu nhưng cơ học. Bạn map invoice data của mình, gồm lines, taxes, totals, parties, sang field name XML của EN 16931. Nhiều thư viện Java/Node/Python làm việc này cho bạn; hãy tìm “factur-x library” trong ngôn ngữ của bạn. Đừng viết từ đầu trừ khi bạn thật sự thích XML schema specs.
Bước 2: render PDF/A-3 và attach XML
Đây là nơi lựa chọn renderer quan trọng.
Không có built-in support: bạn render một PDF thường, rồi post-process bằng công cụ convert sang PDF/A-3 và attach XML như embedded file. Stack phổ biến: Ghostscript + qpdf, hoặc công cụ trả phí như Aspose. Thêm hai bước, thêm hai điểm lỗi, và bạn phải bảo đảm post-processing không làm drift layout thị giác.
Có built-in support (cách gPdf làm): một call.
curl -X POST https://api.gpdf.com/api/v1/e-invoice/render \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data '{
"settings": {
"profile": "pdfa-3b",
"e_invoice": {
"standard": "factur_x",
"profile": "en16931",
"document_type": "invoice",
"xml": {
"format": "cii",
"encoding": "utf8",
"content": "<?xml version=\"1.0\"?><rsm:CrossIndustryInvoice>...</rsm:CrossIndustryInvoice>"
}
}
},
"pages": [{ "size": "a4", "elements": [/* invoice layout */] }]
}' \
--output invoice-with-einvoice.pdf
Đó là toàn bộ pipeline. Renderer emit PDF/A-3b, attach XML của bạn dưới tên factur-x.xml hoặc zugferd-invoice.xml (cả hai đều được mọi consumer nhận biết), rồi trả bytes.
Gotcha phổ biến
Một vài thứ nhiều đội chỉ học được sau khi đau:
“PDF/A” và “font compliant với PDF/A” không giống nhau
File PDF/A-3 yêu cầu mọi font được embed và có coverage đầy đủ cho glyph đã dùng. Nếu invoice có tên khách hàng tiếng Nhật và renderer fallback sang font không embeddable đầy đủ, tool validation sẽ reject. Hãy kiểm tra renderer có embed CJK fonts trong PDF/A mode không; nhiều renderer mặc định không làm.
Visual và XML phải khớp
XML invoice và visual invoice phải đại diện cho cùng một invoice. Auditor thuế sẽ diff chúng. Nếu code emit XML với total: 119.00 nhưng visual PDF hiển thị Total: 120.00 do lỗi rounding hoặc template cũ, bạn có discrepancy thuế trong hồ sơ. Hãy generate cả hai từ cùng source-of-truth, lý tưởng là cùng code path.
Mức “profile” trong EN 16931
Factur-X có các profile: MINIMUM, BASIC, EN 16931, EXTENDED. Chúng khác nhau ở lượng dữ liệu trong XML. Dùng BASIC trừ khi khách hàng yêu cầu cụ thể cao hơn; nó bao phủ tax codes, line items, parties, totals, đủ cho khoảng 95% B2B invoicing. EN 16931 profile thêm chi tiết cho edge cases.
Validate trước khi submit
Luôn validate PDF đã tạo bằng PDF/A validator (veraPDF là chuẩn open-source) và validate XML với EN 16931 schema trước khi gửi tới tax authority. Submission fail tới Chorus Pro / SDI sẽ ảnh hưởng reliability metrics của bạn với regulator.
TL;DR
PDF/A là profile tài liệu self-contained. PDF/A-3 cho phép attach files. Factur-X / ZUGFeRD là “một EN 16931 XML được attach trong PDF/A-3”. Mandate e-invoice trên khắp EU khiến tổ hợp này trở thành format B2B invoice de facto trong giai đoạn 2025-2027.
Nếu renderer xem PDF/A-3 + Factur-X như một config flag duy nhất, migration mang tính cơ học. Nếu không, bạn đang xây một pipeline ops nhiều bước. /api/v1/e-invoice/render của gPdf là phiên bản single-flag; API reference có schema đầy đủ, hoặc bạn có thể thử sample render trong Playground.