如果有人刚跟你说「下个季度开始,发票必须是带 Factur-X 的 PDF/A-3」,你手上唯一的上下文是法务那边丢过来的几个名词——这篇文章就是为你写的。
我们会避开标准文档那种拗口的腔调,把这几个 profile 实际约束了哪些东西、为什么各国政府开始强制它,以及从一份结构化数据出发产出合规 PDF 的最小流水线,讲清楚。
两段话讲完 PDF/A
PDF 是一种很灵活的格式。过于灵活了——同一份 PDF 规范允许嵌入 JavaScript、链接外部资源(五十年后大概率不存在)、用可逆密码学加密、引用外部字体,以及一百种其他让文档「不自洽」的操作。
PDF/A(A 代表 Archival,归档)就是 PDF 的一份 profile,把那些会导致五十年后渲染不出原貌的部分全部禁掉。高层规则:
- 所有字体必须嵌入。
- 不允许 JavaScript、不允许外部链接、不允许音视频。
- 不允许加密。
- 透明度必须扁平化,或者由当前 profile 版本明确支持。
- 颜色必须是设备无关的(强制带 ICC profile)。
- 所有内容必须在文件内部——任何依赖网络的引用都不行。
它有几个版本,每一代多放开一点对新特性的容忍度:
| Profile | 年份 | 新增能力 |
|---|---|---|
| PDF/A-1b | 2005 | 最初基线——最严 |
| PDF/A-2b | 2011 | 允许 JPEG2000、透明度、图层 |
| PDF/A-3b | 2012 | 允许任意文件附件(Factur-X 的技术基石) |
| PDF/A-4 | 2020 | 基于 ISO 32000-2(PDF 2.0),简化了符合性等级 |
后缀「b」代表 basic 符合性(视觉保真)。还有「u」(Unicode 映射)和「a」(可访问性打标)两种变体——对绝大多数发票/票据流来说,「b」就够了,因为税务归档关心的是视觉可复现性,不是屏幕阅读器语义。
工程上的要点:如果你用的渲染器声称支持 PDF/A-3b,理想情况下这应该只是一个配置开关({ profile: "PDF/A-3b" } 或等价写法)。如果非要再跑第二个工具(Ghostscript、qpdf、Acrobat)做后处理,那就是一个流水线缺口,要计入运维成本。
为什么偏偏是 PDF/A-3 关键:它是电子发票的载体
PDF/A-3 新加的一项能力,事后看影响极大:PDF 内部可以挂载任意文件附件。
听起来无聊,但其实不是。这是欧盟当下正在全面铺开的电子发票强制令的全部技术基础。
架构是这样的:一份 PDF 文件同时是
- 一份人类可读的发票(视觉版式、金额、品牌)——人读的那一面。
- 一份机器可读的 XML 发票——税务机关软件解析的那一面。
两者在同一个文件里,代表同一张发票,而 PDF/A-3 外壳保证这份文件几十年后仍然可解析。
两种主要的 XML 格式:
- Factur-X(法国)——基于 UN/CEFACT Cross Industry Invoice 的 XML profile
- ZUGFeRD(德国)——技术上与 Factur-X 一致(两套标准在 2018 年技术合并)
- EN 16931——两边都实现的欧洲标准
对绝大多数业务流程来说,「Factur-X」和「ZUGFeRD」可以互换使用——它们共享同一份 schema、同一种嵌入机制,符合其中一套的 PDF 通常也直接符合另一套。
强制范围:在哪、什么时候、要什么格式
给 2026 年 Q2/Q3 排期的工程团队一份非穷尽快照:
| 国家/地区 | 状态 | 要求格式 |
|---|---|---|
| 德国 | B2B 接收发票自 2025-01-01 起强制;开具自 2027 起强制 | EN 16931(Factur-X / ZUGFeRD / XRechnung) |
| 法国 | 大企业开具自 2026-09 起强制;中小企业 2027-09 | 通过 Chorus Pro 提交 Factur-X |
| 意大利 | B2B 自 2019 起强制 | 通过 SDI 提交 FatturaPA |
| 波兰 | 自 2024-07 起强制 | KSeF |
| 西班牙 | 2026 起强制(B2B) | 通过 FACe 提交 Facturae |
| 比利时 | 自 2026-01 起强制 | Peppol BIS 3 |
整体规律:几乎每一个欧盟成员国都在 2024–2027 这个时间窗里推行某种符合 EN 16931 的电子发票方案。只要你的客户在上述任何一个市场运营,你的 PDF 生成器就得在视觉发票之外附带一份对应的 XML。
最小可用流水线
把标准文档里的繁文缛节先放一边。工程视角的全景是:
┌─────────────────────┐
│ 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 │
└─────────────────────┘
里面两步不算 trivial:
第一步:构造 XML
这步烦,但纯机械。把发票数据(行项、税、总金额、买卖双方)映射到 EN 16931 的 XML 字段名上。Java/Node/Python 都有现成库可用——直接搜「factur-x library」加你的语言。除非真的享受啃 XML schema 规范,不要从头写。
第二步:渲染 PDF/A-3 并挂载 XML
这一步,渲染器的选择就关键了。
没有内建支持的情况:先渲一份普通 PDF,然后用工具后处理转成 PDF/A-3,并且把 XML 作为嵌入文件挂上去。常见组合:Ghostscript + qpdf,或者付费工具如 Aspose。多两步,多两个失败点,还要保证后处理不会让视觉版式漂移。
有内建支持(gPdf 走的路线):一次调用搞定。
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
整条流水线就是这么短。渲染器产出 PDF/A-3b,把 XML 以 factur-x.xml(或 zugferd-invoice.xml,两种命名所有消费方都认)的形式挂载上去,然后把字节流返回。
常见踩坑点
几件事大家通常都是踩过才学会:
「PDF/A」和「字段用了 PDF/A 兼容字体」不是一回事
PDF/A-3 文件要求所有字体都嵌入,而且必须覆盖到所用字形的完整范围。如果发票里出现一个日本客户名、渲染器降级到一份没法完整嵌入的字体,校验工具会直接拒收。检查渲染器在 PDF/A 模式下是不是真的嵌入了 CJK 字体——很多默认不嵌。
视觉和 XML 必须一致
XML 发票和视觉发票本来就该代表同一张发票。税务稽核会逐项比对。如果代码产出的 XML 是 total: 119.00,而视觉 PDF 上写着 Total: 120.00(因为四舍五入 bug 或者一份过期模板),你的档案里就留了一笔税务数据不一致。两边都从同一份事实来源生成,最好走同一段代码路径。
EN 16931 里的「Profile」分级
Factur-X 内部还分 profile:MINIMUM、BASIC、EN 16931、EXTENDED,区别在 XML 里携带多少数据。默认选 BASIC,除非客户明确要求更高——它涵盖税码、行项、买卖双方、总金额,足以覆盖大约 95% 的 B2B 发票场景。EN 16931 这档主要是给边角情形多补点字段。
提交前先校验
正式发给税务系统之前,务必先用 PDF/A 校验器(veraPDF 是开源标准)验一遍 PDF,并且用 EN 16931 schema 验一遍 XML。Chorus Pro / SDI 这些系统拒收次数会算进监管那边的可靠性指标。
TL;DR
PDF/A 是一种「文档自洽」的 profile;PDF/A-3 加了挂载文件的能力;Factur-X / ZUGFeRD 就是「PDF/A-3 里挂一份 EN 16931 XML」。欧盟电子发票强制令把这套组合在 2025–2027 之间推成了事实上的 B2B 发票格式。
如果你用的渲染器把 PDF/A-3 + Factur-X 当成一个配置开关,迁移就是机械活;如果不是,你就是在搭一条多步运维流水线。gPdf 的 /api/v1/e-invoice/render 就是「一个开关」的那种方案——完整 schema 见 API 参考,或者直接在 Playground 跑一份样例渲染。