Skip to content

alpha-service/peppol-billing

Repository files navigation

peppol-bis-billing

Generate and parse Peppol BIS Billing 3.0 (UBL 2.1, EN 16931) invoices and credit notes. Framework-agnostic, fully typed, zero runtime configuration.

npm version npm downloads CI License: MIT TypeScript

From 2026, structured e-invoicing (Peppol) is becoming mandatory for B2B in Belgium and across the EU. This library does the one hard part — turning clean data into spec-compliant UBL 2.1 XML, and parsing inbound documents back into typed JSON — without dragging in an access point, a database, or a web framework.

  • Invoice (type 380) and CreditNote (type 381) generation
  • ✅ Inbound UBL parser → normalized, JSON-safe shape
  • ✅ Belgian VAT → Peppol participant ID (scheme 0208) derivation; explicit IDs for any EAS scheme
  • ✅ Per-line VAT grouping, discounts, IBAN/BIC payment means, multi-currency
  • ✅ Correct XML escaping everywhere; strict TypeScript types
  • ✅ Ships ESM + CJS + .d.ts, no peer framework, one tiny dependency (fast-xml-parser)

Scope & honesty. This produces and reads BIS Billing 3.0 UBL. It does not transmit over the Peppol network (bring your own access point), and it does not run Schematron validation — run the output through the official Peppol BIS validator before going live. Belgian participant-ID derivation is first-class; for other countries pass participantId explicitly.

Install

npm install peppol-bis-billing

Quick start

import { generateInvoice, parseUbl } from "peppol-bis-billing";

const xml = generateInvoice({
  number: "INV-2026-001",
  issueDate: "2026-06-10",
  paymentTermsDays: 30,
  supplier: {
    name: "ALPHA & CO",
    vatNumber: "BE1028386674", // → EndpointID 0208:1028386674
    address: { street: "Ninoofsesteenweg 77", city: "Dilbeek", postalCode: "1700", country: "BE" },
  },
  customer: {
    name: "Client BVBA",
    vatNumber: "BE0123456789",
    address: { city: "Brussels", postalCode: "1000", country: "BE" },
  },
  payment: { iban: "BE68539007547034", bic: "GKCCBEBB" },
  lines: [
    { name: "Carrelage 60x60", quantity: 10, unitPrice: 24.65, vatRate: 21, sku: "PL18805" },
    { name: "Robinet Mitigeur", quantity: 1, unitPrice: 85.5, vatRate: 21 },
  ],
});

// Parse an inbound document
const result = parseUbl(xml);
if (result.ok) {
  console.log(result.document.totals.taxInclusive); // 401.72
}

Credit notes

import { generateCreditNote } from "peppol-bis-billing";

const xml = generateCreditNote({
  number: "CN-2026-001",
  issueDate: "2026-06-12",
  originalInvoice: { number: "INV-2026-001", issueDate: "2026-06-10" }, // BillingReference
  supplier: { name: "ALPHA & CO", vatNumber: "BE1028386674" },
  customer: { name: "Client BVBA", vatNumber: "BE0123456789" },
  lines: [{ name: "Carrelage 60x60", quantity: 2, unitPrice: 24.65, vatRate: 21 }],
});

Participant IDs

import { belgianVatToParticipantId, parseParticipantId } from "peppol-bis-billing";

belgianVatToParticipantId("BE 1028.386.674"); // "0208:1028386674"
parseParticipantId("0208:1028386674");        // { scheme: "0208", value: "1028386674" }

API

Function Purpose
generateInvoice(input): string UBL 2.1 Invoice (type 380), BIS Billing 3.0
generateCreditNote(input): string UBL 2.1 CreditNote (type 381) with BillingReference
parseUbl(xml): ParseResult Parse inbound Invoice/CreditNote → normalized JSON
belgianVatToParticipantId(vat): string Derive 0208:NNNNNNNNNN from a Belgian VAT number
parseParticipantId(id) / resolveParticipant(party) Participant-ID helpers

All input/output types (InvoiceInput, CreditNoteInput, LineItem, ParsedDocument, …) are exported. Field comments reference the EN 16931 business-term IDs (BT-1, BT-9, …).

Discounts

{ name: "Item", quantity: 2, unitPrice: 100, vatRate: 21, discount: { type: "percent", value: 10 } }
// or:                                                     discount: { type: "fixed",   value: 5 } // per unit

VAT

Lines are aggregated into TaxSubtotal groups by (vatCategory, vatRate). Default category is S (standard rate); override per line with vatCategory.

Integrating Peppol into your app

Sending a Peppol invoice has three layers. This library owns the hardest one — the document — and plugs into the rest:

Layer Responsibility This library
1. Document Build valid BIS 3.0 UBL / parse inbound UBL
2. Validation Check against the official Peppol BIS rules (Schematron) ⚠️ external (roadmap)
3. Transport Actually send/receive over the Peppol network ❌ use an Access Point

Outbound: generate → (validate) → send via an Access Point

You still need a registered Peppol Access Point (e.g. Storecove, Peppyrus, Tradeshift, …) to put a document on the network. This library produces the XML the Access Point expects:

import { generateInvoice } from "peppol-bis-billing";

const ubl = generateInvoice(invoiceInput); // BIS 3.0 UBL string

// Optional but recommended before go-live: validate the XML against the official
// Peppol BIS rules (https://docs.peppol.eu/poacc/billing/3.0/). Do this in CI.

// Hand the XML to your Access Point provider. Shape varies per provider:
await fetch("https://api.your-access-point.example/v1/documents", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.AP_API_KEY}`, "Content-Type": "application/xml" },
  body: ubl,
});

Inbound: receive webhook → parse → store

When your Access Point pushes an inbound document (usually a webhook with the raw UBL), parse it into a typed object:

import { parseUbl } from "peppol-bis-billing";

app.post("/webhooks/peppol", (req, res) => {
  const result = parseUbl(req.body.xml);
  if (!result.ok) return res.status(422).json({ error: result.error });

  const { documentId, supplier, totals } = result.document;
  // persist result.document, mark it for review, etc.
  res.sendStatus(200);
});

See examples/ for runnable scripts. This is exactly how the library is used in production: the app builds the XML, the Access Point handles transport.

Development

npm install
npm test          # vitest — round-trip generate↔parse + unit tests
npm run typecheck # tsc --noEmit
npm run build     # tsup → dist (esm + cjs + dts)

Contributing

Contributions welcome — especially more EAS-scheme derivations, additional countries, and Schematron validation. See CONTRIBUTING.md.

License

MIT — see LICENSE.

About

Generate & parse Peppol BIS Billing 3.0 (UBL 2.1, EN 16931) invoices and credit notes. Typed, framework-agnostic.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors