Блог

PDF/A і Factur-X для інженерів без юридичної мови

Що насправді обмежують профілі PDF/A, чому Factur-X важливий для європейських е-рахунків, і найменший практичний pipeline з JSON renderer.

Якщо ви інженер, якому щойно сказали, що “рахунки мають бути PDF/A-3 with Factur-X до наступного кварталу”, а весь ваш контекст полягає в тому, що юридична команда вимовила ці слова, ця стаття для вас.

Ми відкладемо тон документів зі стандартів і пояснимо, що ці профілі насправді обмежують, чому уряди почали їх вимагати, і який найменший практичний pipeline потрібен, щоб випускати compliant PDF зі structured-data renderer.

PDF/A у двох абзацах

PDF — гнучкий формат. Надто гнучкий: та сама PDF spec дозволяє embed JavaScript, посилатися на зовнішні ресурси, яких може не бути через 50 років, шифрувати content зворотною криптографією, reference external fonts і робити десятки інших речей, які позбавляють документ самодостатності.

PDF/A, де “A” означає Archival, — це профіль PDF, який забороняє частини, що можуть завадити документу render identically через 50 років. Правила високого рівня:

  • Усі fonts мають бути embedded.
  • Немає JavaScript, зовнішніх links, audio/video.
  • Немає encryption.
  • Уся transparency має бути flatten або підтримуватися версією профілю.
  • Colours мають бути device-independent; потрібен ICC profile.
  • Увесь content має бути всередині file; жодних references, що залежать від network.

Є кілька versions, кожна додає tolerance для новіших features:

ProfileYearЩо додає
PDF/A-1b2005Original baseline, найсуворіший
PDF/A-2b2011Дозволяє JPEG2000, transparency, layers
PDF/A-3b2012Дозволяє arbitrary file attachments, основу Factur-X
PDF/A-42020База ISO 32000-2 (PDF 2.0), спрощені conformance levels

Суфікс “b” означає “basic” conformance, тобто visual fidelity. Є також variants “u” для unicode-mapped і “a” для accessibility-tagged. Для більшості invoice/receipt workflows потрібен саме “b”, бо податкова архівація дбає про visual reproducibility, а не про screen-reader semantics.

Практичний висновок: якщо renderer каже, що підтримує PDF/A-3b, це має бути single config flag, наприклад { profile: "PDF/A-3b" } або еквівалент. Якщо доводиться запускати другу tool, таку як Ghostscript, qpdf або Acrobat, щоб конвертувати після render, це workflow gap, який потрібно закласти в ops.

Чому PDF/A-3 особливо важливий: це носій е-рахунків

PDF/A-3 додав одну capability, яка здається буденною, але виявилася фундаментальною: arbitrary file attachments всередині PDF.

Звучить нудно. Насправді ні. Це вся технічна основа e-invoice mandates, які зараз розгортаються в Європі.

Архітектура: один PDF file, який є одночасно:

  1. Human-readable invoice з visual layout, totals і branding.
  2. Machine-readable XML invoice, який parse робить software податкового органу.

Обидва всередині одного file, обидва представляють той самий invoice, а PDF/A-3 wrapper гарантує, що file залишиться parseable десятиліттями.

Основні XML formats:

  • Factur-X (France), XML profile на базі UN/CEFACT Cross Industry Invoice.
  • ZUGFeRD (Germany), по суті ідентичний Factur-X; два стандарти технічно об’єдналися у 2018 році.
  • EN 16931, європейська норма, якій відповідають обидві implementations.

Для більшості workflows “Factur-X” і “ZUGFeRD” — взаємозамінні терміни. Вони share schema, share embedding mechanism, і PDF compliant з одним зазвичай compliant з іншим.

Що обов’язкове, де і коли

Невичерпний snapshot для engineers, які планують Q2/Q3 2026 rollouts:

CountryStatusRequired format
GermanyB2B mandatory для invoice receipt з 2025-01-01; з 2027 також для issuingEN 16931 (Factur-X / ZUGFeRD / XRechnung)
Francemandatory issuing для large enterprises 2026-09; SMEs 2027-09Factur-X via Chorus Pro
ItalyB2B mandatory since 2019FatturaPA via SDI
PolandMandatory since 2024-07KSeF
SpainMandatory from 2026 (B2B)Facturae via FACe
BelgiumMandatory from 2026-01Peppol BIS 3

Патерн такий: кожна EU member state впроваджує певний flavour EN 16931-compliant e-invoicing у timeline 2024-2027. Якщо ваші customers працюють у будь-якому з цих markets, ваш PDF generator має emit attached XML поруч із visual invoice.

Найменший практичний pipeline

Забудьте на мить, що наказують standards documents. Engineering view такий:

   ┌─────────────────────┐
   │  Your invoice data  │  (вже десь JSON object)
   └─────────┬───────────┘


   ┌─────────────────────┐
   │ Build EN 16931 XML  │  (deterministic mapping; tested libs існують)
   └─────────┬───────────┘


   ┌─────────────────────┐
   │ Render PDF/A-3b +   │
   │ attach the XML      │  (single API call до gPdf, або two-step elsewhere)
   └─────────┬───────────┘


   ┌─────────────────────┐
   │  Hand off to        │
   │  Chorus Pro / SDI / │
   │  Peppol / etc       │
   └─────────────────────┘

Два non-trivial steps:

Step 1: побудуйте XML

Це дратує, але механічно. Ви map invoice data, наприклад lines, taxes, totals і parties, на EN 16931 XML field names. Є кілька Java/Node/Python libraries, які роблять це за вас; шукайте “factur-x library” для вашої language. Не пишіть з нуля, якщо ви справді не любите XML schema specs.

Step 2: render PDF/A-3 і attach XML

Тут має значення вибір renderer.

Без built-in support: ви render ordinary PDF, потім post-process tool’ом, який конвертує в PDF/A-3 і attach XML як embedded file. Common stacks: Ghostscript + qpdf або paid tool на кшталт Aspose. Це два extra steps, дві extra failure points, і потрібно стежити, щоб post-processing не змінив visual layout.

З built-in support (підхід gPdf): one call.

curl -X POST https://gpdf.com/api/v1/e-invoice/render \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "document": {
      "pages": [{ "size": "a4", "elements": [...] }]
    },
    "einvoice": {
      "format": "factur-x",
      "profile": "BASIC",
      "xml": "<rsm:CrossIndustryInvoice>...</rsm:CrossIndustryInvoice>"
    }
  }' \
  --output invoice-with-einvoice.pdf

Це весь pipeline. Renderer emit PDF/A-3b, attach ваш XML як factur-x.xml або zugferd-invoice.xml, обидва filenames розпізнають consumers, і повертає bytes.

Поширені пастки

Кілька речей, які люди часто вивчають важким шляхом:

“PDF/A” і “з PDF/A-compliant fonts” не те саме

PDF/A-3 file вимагає, щоб усі fonts були embedded з повним character coverage для used glyphs. Якщо invoice містить Japanese customer name, а renderer падає на fallback font, який не fully embeddable, validation tools його reject. Перевірте, що ваш renderer embed CJK fonts у PDF/A mode; багато хто не робить цього by default.

Visual + XML мають збігатися

XML invoice і visual invoice мають представляти той самий invoice. Tax auditors будуть їх diff. Якщо code emit XML з total: 119.00, а visual PDF показує Total: 120.00 через rounding bug або stale template, у вас tax discrepancy у file. Generate обидва з same source-of-truth, ideally в одному code path.

”Profile” levels в EN 16931

Factur-X має profiles: MINIMUM, BASIC, EN 16931, EXTENDED. Вони відрізняються кількістю data в XML. Використовуйте BASIC, якщо customer specifically не вимагає більше; він покриває tax codes, line items, parties і totals, чого достатньо для приблизно 95% B2B invoicing. EN 16931 profile додає further detail для edge cases.

Validation перед submission

Завжди validate generated PDF через PDF/A validator; veraPDF є open-source standard. Також validate XML проти EN 16931 schema перед відправкою до tax authority. Failed submissions до Chorus Pro / SDI впливають на ваші reliability metrics у regulator.

TL;DR

PDF/A — це self-contained-document profile. PDF/A-3 дозволяє attach files. Factur-X / ZUGFeRD означає “EN 16931 XML attached всередині PDF/A-3”. EU e-invoice mandates роблять цю комбінацію de facto B2B invoice format у 2025-2027.

Якщо renderer treats PDF/A-3 + Factur-X як single config flag, migration механічна. Якщо ні, ви будуєте multi-step ops pipeline. /api/v1/e-invoice/render у gPdf — single-flag version; повна schema є в API reference, або спробуйте sample render у Playground.