311 lines
10 KiB
Markdown
311 lines
10 KiB
Markdown
# @fin.cx/einvoice ⚡
|
|
|
|
TypeScript e-invoicing for the real world: load invoice XML or hybrid PDFs, detect the format, map it into a typed invoice model, validate it, convert it, and embed XML back into PDFs.
|
|
|
|
`@fin.cx/einvoice` is built for programmers who need a practical toolkit around European e-invoice formats without writing a parser zoo from scratch.
|
|
|
|
## Issue Reporting and Security
|
|
|
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
|
|
## Why this library?
|
|
|
|
- 🚀 Load invoices from XML strings, files, or PDFs with embedded XML.
|
|
- 🧭 Detect `ubl`, `xrechnung`, `cii`, `facturx`, `zugferd`, and `fatturapa` documents.
|
|
- 🧾 Work on a typed in-memory invoice model based on `@tsclass/tsclass`.
|
|
- ✅ Validate invoices on syntax, semantic, and business-rule levels.
|
|
- 🔄 Export invoices as `facturx`, `zugferd`, `xrechnung`, `ubl`, or `cii`.
|
|
- 📎 Extract XML from invoice PDFs and attach XML back into existing PDFs.
|
|
- 🧱 Use the high-level `EInvoice` class or lower-level decoders, encoders, validators, and PDF extractors directly.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
pnpm add @fin.cx/einvoice
|
|
```
|
|
|
|
`postinstall` will try to download Schematron validation resources on a best-effort basis when the package is installed in a normal online environment. Install will not fail if the resources cannot be downloaded.
|
|
|
|
Useful commands:
|
|
|
|
```bash
|
|
pnpm download-schematron
|
|
pnpm download-test-samples
|
|
```
|
|
|
|
Useful environment flag:
|
|
|
|
```bash
|
|
EINVOICE_SKIP_RESOURCES=1
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```ts
|
|
import { EInvoice, ValidationLevel } from '@fin.cx/einvoice';
|
|
|
|
const invoice = await EInvoice.fromFile('./invoice.xml');
|
|
|
|
console.log(invoice.getFormat());
|
|
console.log(invoice.id);
|
|
console.log(invoice.from.name, '->', invoice.to.name);
|
|
|
|
const validation = await invoice.validate(ValidationLevel.BUSINESS);
|
|
if (!validation.valid) {
|
|
console.log(validation.errors);
|
|
}
|
|
|
|
const xrechnungXml = await invoice.exportXml('xrechnung');
|
|
```
|
|
|
|
## What it can do
|
|
|
|
### Load and inspect invoices
|
|
|
|
```ts
|
|
import { EInvoice } from '@fin.cx/einvoice';
|
|
|
|
const fromXml = await EInvoice.fromXml(xmlString);
|
|
const fromFile = await EInvoice.fromFile('./invoice.xml');
|
|
const fromPdf = await EInvoice.fromPdf(pdfBuffer);
|
|
|
|
console.log(fromXml.subject);
|
|
console.log(fromXml.items.length);
|
|
console.log(fromXml.currency);
|
|
```
|
|
|
|
### Create invoices in code
|
|
|
|
```ts
|
|
import { EInvoice } from '@fin.cx/einvoice';
|
|
|
|
const invoice = new EInvoice();
|
|
|
|
invoice.accountingDocId = 'INV-2026-001';
|
|
invoice.issueDate = new Date('2026-04-16');
|
|
invoice.currency = 'EUR';
|
|
invoice.dueInDays = 14;
|
|
|
|
invoice.from = {
|
|
type: 'company',
|
|
name: 'Sender GmbH',
|
|
description: '',
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
address: {
|
|
streetName: 'Example Street',
|
|
houseNumber: '1',
|
|
city: 'Berlin',
|
|
postalCode: '10115',
|
|
country: 'DE',
|
|
},
|
|
registrationDetails: {
|
|
vatId: 'DE123456789',
|
|
registrationId: 'HRB 123456',
|
|
registrationName: 'Sender GmbH',
|
|
},
|
|
};
|
|
|
|
invoice.to = {
|
|
type: 'company',
|
|
name: 'Receiver SAS',
|
|
description: '',
|
|
status: 'active',
|
|
foundedDate: { year: 2020, month: 1, day: 1 },
|
|
address: {
|
|
streetName: 'Rue Example',
|
|
houseNumber: '10',
|
|
city: 'Paris',
|
|
postalCode: '75001',
|
|
country: 'FR',
|
|
},
|
|
registrationDetails: {
|
|
vatId: 'FR12345678901',
|
|
registrationId: 'RCS 123456789',
|
|
registrationName: 'Receiver SAS',
|
|
},
|
|
};
|
|
|
|
invoice.items = [
|
|
{
|
|
position: 1,
|
|
name: 'Implementation work',
|
|
articleNumber: 'IMPL-001',
|
|
unitType: 'HUR',
|
|
unitQuantity: 8,
|
|
unitNetPrice: 120,
|
|
vatPercentage: 19,
|
|
},
|
|
];
|
|
|
|
const xml = await invoice.exportXml('facturx');
|
|
```
|
|
|
|
### Validate at different levels
|
|
|
|
```ts
|
|
import { EInvoice, ValidationLevel } from '@fin.cx/einvoice';
|
|
|
|
const invoice = await EInvoice.fromXml(xmlString);
|
|
|
|
const syntax = await invoice.validate(ValidationLevel.SYNTAX);
|
|
const semantic = await invoice.validate(ValidationLevel.SEMANTIC);
|
|
const business = await invoice.validate(ValidationLevel.BUSINESS, {
|
|
featureFlags: ['EN16931_BUSINESS_RULES', 'CODE_LIST_VALIDATION'],
|
|
reportOnly: true,
|
|
});
|
|
|
|
console.log(business.valid);
|
|
console.log(business.errors);
|
|
console.log(business.warnings ?? []);
|
|
```
|
|
|
|
### Convert between supported XML targets
|
|
|
|
```ts
|
|
import { EInvoice } from '@fin.cx/einvoice';
|
|
|
|
const invoice = await EInvoice.fromFile('./source.xml');
|
|
|
|
const facturxXml = await invoice.exportXml('facturx');
|
|
const zugferdXml = await invoice.exportXml('zugferd');
|
|
const xrechnungXml = await invoice.exportXml('xrechnung');
|
|
const ublXml = await invoice.exportXml('ubl');
|
|
const ciiXml = await invoice.exportXml('cii');
|
|
```
|
|
|
|
### Work with PDFs
|
|
|
|
```ts
|
|
import { EInvoice, PDFExtractor } from '@fin.cx/einvoice';
|
|
|
|
const extracted = await new PDFExtractor().extractXml(pdfBuffer);
|
|
if (extracted.success && extracted.xml) {
|
|
const invoice = await EInvoice.fromXml(extracted.xml);
|
|
console.log(extracted.format, invoice.id);
|
|
}
|
|
|
|
const invoice = await EInvoice.fromXml(xmlString);
|
|
const pdfWithXml = await invoice.embedInPdf(existingPdfBuffer, 'facturx');
|
|
```
|
|
|
|
## Supported formats
|
|
|
|
| Format | Detect | Import | Export | Validation | Notes |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| `ubl` | Yes | Yes | Yes | Yes | Generic UBL flow |
|
|
| `xrechnung` | Yes | Yes | Yes | Yes | UBL-based profile |
|
|
| `cii` | Yes | Yes | Yes | Partial | Generic CII export currently uses the Factur-X encoder path |
|
|
| `facturx` | Yes | Yes | Yes | Yes | Main CII-based generation path |
|
|
| `zugferd` | Yes | Yes | Yes | Partial | Input supports v1 and v2+, export focuses on the current encoder |
|
|
| `fatturapa` | Yes | No | No | Basic placeholder | Detection exists, decoder/encoder do not |
|
|
|
|
### Important format notes
|
|
|
|
- There is no dedicated root-level `peppol` export format. PEPPOL-specific checks exist, but the XML export targets are still `ubl` / `xrechnung`.
|
|
- `FatturaPA` should be documented as detection-only right now.
|
|
- `creditnote` / `debitnote` handling exists in parts of the lower-level code, but the high-level `invoiceType` setter on `EInvoice` only accepts `'invoice'`.
|
|
|
|
## Public API overview
|
|
|
|
Top-level exports from `@fin.cx/einvoice` include:
|
|
|
|
- `EInvoice`
|
|
- `createEInvoice()`
|
|
- `validateXml(xml, level?)`
|
|
- `FormatDetector`
|
|
- `PDFExtractor`, `PDFEmbedder`
|
|
- `DecoderFactory`, `EncoderFactory`, `ValidatorFactory`
|
|
- `BaseDecoder`, `BaseEncoder`, `BaseValidator`
|
|
- `UBLBase*` and `CIIBase*` extension classes
|
|
- `FacturX*` and `ZUGFeRD*` format-specific classes
|
|
- `ValidationLevel`, `InvoiceFormat`
|
|
- exported types like `TInvoice`, `ValidationResult`, `ValidationError`, `ExportFormat`, `IPdf`, `EInvoiceOptions`
|
|
|
|
## Validation resources and install-time behavior
|
|
|
|
The package includes an install bootstrap in `ts_install/` that tries to download validation resources into `assets_downloaded/schematron`.
|
|
|
|
Behavior to know:
|
|
|
|
- It only runs in a real `postinstall` lifecycle.
|
|
- It skips cleanly when offline.
|
|
- It skips when `dist_ts/` is not present yet.
|
|
- It can be disabled in CI with `EINVOICE_SKIP_RESOURCES=1`.
|
|
- Missing resources do not break install, but they can reduce advanced validation coverage.
|
|
|
|
If you need the resources manually:
|
|
|
|
```bash
|
|
pnpm download-schematron
|
|
pnpm download-test-samples
|
|
```
|
|
|
|
## Architecture in one screen
|
|
|
|
```text
|
|
XML / PDF
|
|
-> format detection
|
|
-> decoder
|
|
-> EInvoice model
|
|
-> validation / editing
|
|
-> encoder
|
|
-> XML / PDF with embedded XML
|
|
```
|
|
|
|
This repo also exposes the lower-level pieces separately so you can plug into the pipeline at the stage you actually need.
|
|
|
|
## Good things to know before using it
|
|
|
|
- `embedInPdf()` attaches XML to an existing PDF buffer. It does not render invoice PDFs from scratch.
|
|
- `saveToFile('something.pdf', ...)` only works when `invoice.pdf` already exists, for example after loading from a PDF first.
|
|
- Validation coverage is useful and broad, but this README deliberately does not claim blanket certification or “100% compliance”.
|
|
- Schematron integration and richer business-rule layers exist, but they depend on downloaded resources and are not the same as guaranteed certification against every profile.
|
|
|
|
## Example workflow
|
|
|
|
```ts
|
|
import { EInvoice, ValidationLevel } from '@fin.cx/einvoice';
|
|
|
|
const invoice = await EInvoice.fromFile('./incoming.pdf');
|
|
|
|
const validation = await invoice.validate(ValidationLevel.BUSINESS, {
|
|
featureFlags: ['EN16931_BUSINESS_RULES', 'CODE_LIST_VALIDATION'],
|
|
reportOnly: false,
|
|
});
|
|
|
|
if (!validation.valid) {
|
|
throw new Error(validation.errors.map((error) => error.message).join('\n'));
|
|
}
|
|
|
|
const xmlForGermanB2G = await invoice.exportXml('xrechnung');
|
|
```
|
|
|
|
## Module layout
|
|
|
|
- `ts/`: main published API and implementation
|
|
- `ts_install/`: install-time resource bootstrap and download helpers
|
|
- `assets_downloaded/`: downloaded validation resources
|
|
- `test/`: corpus-heavy validation, format, PDF, performance, and security coverage
|
|
|
|
## License and Legal Information
|
|
|
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./license) file.
|
|
|
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
|
|
### Trademarks
|
|
|
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
|
|
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
|
|
|
### Company Information
|
|
|
|
Task Venture Capital GmbH
|
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
|
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
|
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|