Skip to content

ksef4dev/ksef-fop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

363 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Renovate enabled Maven Central Quality Gate Status

Table of contents

General information

PDF generator for KSeF

Our PDF Generator allows you to automatically create UPO and invoice documents in PDF format from KSeF XML data. The layout and styling of the generated PDFs are designed to closely match the visualisation available in the official KSeF taxpayer application (Aplikacja Podatnika KSeF), so you can produce consistent, professional-looking documents without manual data processing.

What do you need to use it in your application:

  1. Fonts if you want Polish diacritical letters
  2. FOP config file
  3. Dependency io.alapierre.ksef:ksef-fop

Technologies

  • Java 21
  • Apache FOP

Security considerations

The library processes two types of XML input:

  • XSLT stylesheets (trusted, bundled with the library or provided by the developer)
  • E-invoices / UPO documents (untrusted, provided by the caller)

Starting from version 1.2.20, the XML parser is configured in a secure mode:

  • external entity resolution is disabled
  • DOCTYPE declarations are rejected

This mitigates common XML-based attacks such as XXE and entity expansion DoS.

Important

Applications should still treat input XML as untrusted data and apply validation as appropriate to their context.

Configuration

Example FOP config
<fop version="1.0">
    <renderers>
        <renderer mime="application/pdf">
            <fonts>
                <font kerning="yes" embed-url="file:fonts/OpenSans-Regular.ttf">
                    <font-triplet name="sans" style="normal" weight="normal"/>
                </font>
                <font kerning="yes" embed-url="file:fonts/OpenSans-Bold.ttf">
                    <font-triplet name="sans" style="normal" weight="bold"/>
                </font>
            </fonts>
        </renderer>
    </renderers>
</fop>

Tailor your font path and name - FOP template uses font family name sans. You can read more about fonts in FOP here: https://xmlgraphics.apache.org/fop/0.95/fonts.html

Invoices

The PDF invoice generator currently offers the following features:

  • Invoice schemas: support for structured invoice FA3 (FA3_1_0_E).
  • Basic data: invoice number, type, KSeF number, issue and sale dates, place of issue.
  • Parties: Seller (Entity 1), Buyer (Entity 2), and optional third party (name, address, contact details).
  • Line items and summaries: list of goods/services with prices and quantities, VAT summary, payment details, bank account number.
  • Verification data: QR code and verification link (KOD I in ONLINE mode; KOD I + KOD II in OFFLINE mode) configured via InvoiceQRCodeGeneratorRequest (URLs or parameters for link generation).
  • Logo: invoice logo from bytes (logo) or from URI (logoUri).
  • Additional options: currency date (currencyDate), issuer user (issuerUser), highlight differences on correction invoices (showCorrectionDifferences)
  • Localization: Support PL and EN languages.
  • Attachment: Support XSD Attachment (Zalacznik).

Examples

Generate UPO
PdfGenerator generator = new PdfGenerator("fop.xconf");

try (OutputStream out = new BufferedOutputStream(new FileOutputStream("upo.pdf"))) {
    InputStream xml = new FileInputStream("upo.xml");
    Source src = new StreamSource(xml);
    UpoGenerationParams params = UpoGenerationParams.builder()
            .schema(UpoSchema.UPO_V3)
            .language(Language.PL)
            .build();
    generator.generateUpo(src, params, out);
}
Generate Invoice (FA3 with online QR)
PdfGenerator generator = new PdfGenerator(new FileInputStream("fop.xconf"));
byte[] invoiceXml = Files.readAllBytes(Paths.get("invoice.xml"));

String verificationLink = "https://qr-test.ksef.mf.gov.pl/invoice/NIP/data/TOKEN";
InvoiceQRCodeGeneratorRequest qrRequest = InvoiceQRCodeGeneratorRequest.onlineQrBuilder(verificationLink);
InvoiceGenerationParams params = InvoiceGenerationParams.builder()
        .schema(InvoiceSchema.FA3_1_0_E)
        .ksefNumber("1234567890-20231221-XXXXXXXX-XX")
        .invoiceQRCodeGeneratorRequest(qrRequest)
        .language(Language.PL)
        .build();

try (OutputStream out = new BufferedOutputStream(new FileOutputStream("invoice.pdf"))) {
    generator.generateInvoice(invoiceXml, params, out);
}

For OFFLINE invoices (two QR codes: KOD I + KOD II), use InvoiceQRCodeGeneratorRequest.offlineCertificateQrBuilder(onlineQrCodeUrl, certificateQrCodeUrl) or the variant with parameters (environment URL, NIP, date, certificate, etc.) — see the InvoiceQRCodeGeneratorRequest Javadoc for details.

Custom templates

By default the library picks a built-in XSL-FO template based on the InvoiceSchema value (FA2_1_0_Etemplates/fa2/ksef_invoice.xsl, FA3_1_0_Etemplates/fa3/ksef_invoice.xsl). If you need full control over the PDF layout you can provide your own XSL-FO stylesheet instead.

Set templatePath on InvoiceGenerationParams to a classpath-relative path pointing to your custom XSL file:

InvoiceGenerationParams params = InvoiceGenerationParams.builder()
        .schema(InvoiceSchema.FA3_1_0_E)
        .ksefNumber("1234567890-20231221-XXXXXXXX-XX")
        .templatePath("templates/custom/my_invoice.xsl")
        .build();

When templatePath is set the library uses it directly; when it is null (the default) the template is resolved automatically from the schema.

HTTP(S) URL as templatePath (catalog): you may pass a URL such as http://localhost:8077/xslt/ksef_invoice so it lines up with a template-server route. TemplateResolver does not download templates from arbitrary hosts: every allowed http(s): stylesheet URI must appear in classpath:catalog.xml, mapped to a trusted classpath resource (typically your packaged XSLT). Nested xsl:import / xsl:include that use URLs must be catalogued the same way. See HttpTemplatePathInvoiceTest and src/test/resources/catalog.xml.

Security note: templatePath is resolved through TemplateResolver (classpath / template roots / catalog-mapped HTTPS). Make sure untrusted users cannot control this value or inject catalog entries.

Remote HTTP template fetching

When InvoiceGenerationParams.resourceRoots (or UpoGenerationParams.resourceRoots) includes an http: or https: base URL, ksef-fop fetches stylesheets, includes, logos and label files over HTTP from that base. The actual HTTP client is pluggable via RemoteResourceFetcher (a @FunctionalInterface with a single fetch(URI) method).

Default behaviour

If the host application does nothing, ksef-fop uses HttpURLConnectionRemoteResourceFetcher: GET only, no redirect following, 10 s timeouts, 16 MB body cap. URL containment (staying within the configured resource root, rejecting ../ escapes) is enforced by UrlResourceRoot before the fetcher is called.

Registering a custom client

Host applications that embed ksef-fop (e.g. a Micronaut service with its own HttpClient) register an implementation at startup:

RemoteResourceFetchers.setGlobal(myFetcher);

TemplateResolver obtains the active fetcher via RemoteResourceFetchers.get() — you do not pass it through InvoiceGenerationParams.

For tests and stubs, a lambda is enough:

RemoteResourceFetcher stub = uri -> "<xsl:stylesheet version=\"1.0\"/>".getBytes(StandardCharsets.UTF_8);

Host application responsibility

Important

Providing a correct RemoteResourceFetcher implementation (or consciously using the built-in default) is the responsibility of the application that embeds ksef-fop. The library validates that fetched URLs stay under configured HTTP resource roots, but it cannot audit your HTTP stack. An incorrect or careless custom fetcher can introduce serious vulnerabilities.

Risk What the built-in default does What your implementation must guarantee
SSRF Only URLs already accepted by UrlResourceRoot containment are passed to fetch Do not widen scope (e.g. follow redirects to external hosts, honour X-Forwarded-* overrides, or fetch URLs outside the passed URI)
Unbounded downloads 16 MB response cap Enforce a comparable body size limit
Redirect abuse Redirects disabled Do not follow redirects; ksef-fop validates only the original URL before calling fetch
Credential leakage No auth headers sent If you propagate Authorization or cookies, restrict to trusted template-server URLs only
Untrusted content N/A (transport) Fetch only from operator-controlled template servers; treat response bytes as trusted input to XSLT only when the source is trusted
TLS JVM default trust store Use HTTPS and appropriate certificate validation for production template servers

If you do not need a custom HTTP stack, prefer the default — do not register a fetcher unless you have a concrete integration requirement (framework HttpClient, observability, auth propagation).

Custom translations

The library ships with built-in Polish and English translations for all PDF labels (invoice headers, column names, annotations, QR code captions, etc.). Labels live as XML documents at i18n/labels.xml (base / Polish) and i18n/labels_<lang>.xml per locale. The XSLT stylesheets load these files at runtime via document(), routed through the same URIResolver that resolves custom templates. This means label overrides use exactly the same mechanism as template overrides — there is no separate "translation root" concept: overrides live under InvoiceGenerationParams.resourceRoots.

1. Create your labels XML file(s)

Drop them next to any templates you might be overriding, under an i18n/ directory inside one of your resourceRoots:

/etc/ksef/i18n/labels.xml (Polish / base):

<?xml version="1.0" encoding="UTF-8"?>
<labels>
  <entry key="seller">Dostawca</entry>
  <entry key="buyer">Klient</entry>
</labels>

/etc/ksef/i18n/labels_en.xml (English):

<?xml version="1.0" encoding="UTF-8"?>
<labels>
  <entry key="seller">Vendor</entry>
  <entry key="buyer">Client</entry>
</labels>

2. Point resourceRoots at the directory containing i18n/

InvoiceGenerationParams params = InvoiceGenerationParams.builder()
        .schema(InvoiceSchema.FA3_1_0_E)
        .ksefNumber("1234567890-20231221-XXXXXXXX-XX")
        .language(InvoiceLanguage.EN)
        .resourceRoot(Paths.get("/etc/ksef").toUri())
        .build();

PdfGenerator generator = new PdfGenerator("fop.xconf");
generator.generateInvoice(invoiceXml, params, out);

That's it. Anywhere the XSLT looks up a key (key('kLabels', …, $labels)), it will see the merged view. No extra plumbing — the same resolver is shared between the stylesheet and the QR-code label lookups.

Lookup order

Per language, candidates are tried most-specific first (labels_en_US.xmllabels_en.xmllabels.xml). For each candidate the resolver walks:

  1. Each configured filesystem root, in insertion order
  2. The library classpath (built-in defaults)

Within the XSLT the locale-specific file wins key-by-key over the base file:

Source Keys contributed
labels_<lang>.xml (override or classpath) All keys it contains
labels.xml (override or classpath) Any keys not in the locale file

So a partial locale override merges cleanly with the base file; keys missing from the locale file fall through to Polish defaults (or your overridden Polish base, if you supplied one).

Note: an override replaces the library's file at the same path. If your labels_en.xml has only seller, other English keys come from the base file (Polish built-ins or your labels.xml override). Copy the library file in full if you want to keep English defaults while changing a single key.

Security

  • Each root is canonicalised via Path.toRealPath(); non-existing or non-directory roots are rejected up front.
  • Every resource loaded from a root must remain under it (.. escapes and symlink escapes are rejected — the lookup silently falls through to the classpath).
  • The XML parser used for labels runs with FEATURE_SECURE_PROCESSING, DTDs disabled, and external entity resolution disabled — overrides cannot perform XXE attacks.

Using TranslationService directly

You rarely need to touch this class: PdfGenerator creates a per-invocation instance from your resourceRoots. If you need to look up labels from Java (e.g. for tooling or tests), pass your own URIResolver (most likely a TemplateResolver):

URIResolver resolver = new TemplateResolver(
        Collections.singletonList(Paths.get("/etc/ksef").toUri()));
TranslationService ts = new TranslationService(resolver);

String label = ts.getTranslation("en", "seller"); // "Vendor"
Document merged = ts.getTranslationsAsXml("en");  // full merged document

Tip: the full list of available translation keys lives in the library's i18n/labels.xml and i18n/labels_en.xml (under src/main/resources/i18n/).

Custom properties

Built-in parameters (nrKsef, logo, currencyDate, …) cover the standard use cases, but a custom template will almost certainly need extra, template-specific data - company branding fields, feature flags, additional metadata, etc.

InvoiceGenerationParams exposes a customProperties map for exactly this purpose. Every entry in the map is forwarded as an XSLT parameter to the transformer, so your template can declare and use it like any other parameter.

InvoiceGenerationParams params = InvoiceGenerationParams.builder()
        .schema(InvoiceSchema.FA3_1_0_E)
        .ksefNumber("1234567890-20231221-XXXXXXXX-XX")
        .templatePath("templates/custom/my_invoice.xsl")
        .customProperties(Map.of(
                "companySlogan", "We deliver on time!",
                "showWatermark", true
        ))
        .build();

In your XSL template declare matching parameters and use them normally:

<xsl:param name="companySlogan"/>
<xsl:param name="showWatermark"/>

<fo:block font-size="8pt">
    <xsl:value-of select="$companySlogan"/>
</fo:block>

Custom properties are applied after all built-in parameters, so a key that collides with a built-in name will override it - use distinct names to avoid surprises.

Security note: customProperties entries are forwarded as-is to the XSLT transformer. Make sure untrusted users cannot control parameter names or values when rendering trusted templates.

The map defaults to an empty HashMap, so existing callers that do not set it are unaffected.

FOP Schema

https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk/fop/src/foschema/fop.xsd

References

License

This project is licensed under the Apache License, Version 2.0.

About

eInvoice FA(3) KSeF PDF generation module

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors