peppol-bis-billing
v0.1.0
Published
Generate and parse Peppol BIS Billing 3.0 (UBL 2.1, EN 16931) invoices and credit notes. Framework-agnostic, typed, zero runtime config.
Maintainers
Readme
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.
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
participantIdexplicitly.
Install
npm install peppol-bis-billingQuick 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 unitVAT
Lines are aggregated into TaxSubtotal groups by (vatCategory, vatRate). Default category is S (standard rate); override per line with vatCategory.
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.
