einvoice

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

README

go-einvoice

CI Go Reference Go Report Card

A Go library for generating German E-Invoices (E-Rechnung) compliant with EN 16931.

This library is designed to help developers upgrade their invoicing systems for the upcoming German B2B mandates starting January 1st, 2025. It supports the creation of XRechnung 3.0 (XML) and ZUGFeRD / Factur-X (PDF/A-3 with embedded XML).

Features

✅ Full support for EN 16931 (UN/CEFACT CII). ✅ Compliant with XRechnung 3.0 (German Standard). ✅ Generates ZUGFeRD / Factur-X files by embedding XML into existing PDFs. ✅ Parses incoming e-invoices (CII and UBL syntax) back into the domain model, including extraction of the embedded XML from ZUGFeRD / Factur-X PDFs.

Reading e-invoices

// Standalone XML (XRechnung CII or UBL, Peppol BIS):
parsed, err := einvoice.Parse(xmlBytes)

// ZUGFeRD / Factur-X PDF — extract the attachment first:
xmlBytes, err := zugferd.ExtractXML(pdfBytes)
parsed, err := einvoice.Parse(xmlBytes)

fmt.Println(parsed.Seller.Name, parsed.Number, parsed.Totals.GrandTotal)

Parse is lenient by design: it returns the amounts as stated in the document (Totals, Taxes) and never recomputes them, so consumers can cross-check stated vs. derived values.

Installation

go get github.com/dotwavehq/go-einvoice

Library Usage

To use go-einvoice in your backend service, you build an einvoice.Invoice and hand it to a serializer. The library takes care of the complex XML namespaces and business rules.

Create an Invoice

import (
    einvoice "github.com/dotwavehq/go-einvoice"
    "github.com/dotwavehq/go-einvoice/cii"
    "github.com/dotwavehq/go-einvoice/zugferd"
    "github.com/shopspring/decimal"
)

func main() {
    // Helper to create decimals from string to avoid float precision errors
    toDec := func(s string) decimal.Decimal {
        d, _ := decimal.NewFromString(s)
        return d
    }

    invoice := einvoice.Invoice{
        Number:         "RE-2025-1001",
        IssueDate:      time.Now(),
        DueDate:        time.Now().AddDate(0, 0, 14),
        DeliveryDate:   time.Now(), // BT-72, required under German VAT law
        BuyerReference: "ORDER-12345",
        Currency:       "EUR",
        Note:           "Thank you for your business.",

        Seller: einvoice.Party{
            Name:             "My Software Company GmbH",
            Street:           "Tech Lane 1",
            City:             "Berlin",
            PostalCode:       "10115",
            CountryCode:      "DE",
            VATID:            "DE123456789",
            ElectronicAddress: "billing@mycompany.com", // BT-34, mandatory in XRechnung
            Contact: &einvoice.Contact{
                Name:  "Jane Doe",
                Phone: "+49 30 123456",
                Email: "billing@mycompany.com",
            },
        },

        Buyer: einvoice.Party{
            Name:             "Client Corp AG",
            Street:           "Business Rd 5",
            City:             "Munich",
            PostalCode:       "80331",
            CountryCode:      "DE",
            VATID:            "DE987654321",
            ElectronicAddress: "ap@clientcorp.de", // BT-49, mandatory in XRechnung
        },

        Payment: einvoice.Payment{
            IBAN:             "DE99123456789012345678",
            PaymentMeansCode: "30",
        },

        LineItems: []einvoice.LineItem{
            {
                Description: "Consulting Services",
                Quantity:    toDec("10.0"),
                UnitCode:    "HUR",
                UnitPrice:   toDec("100.00"),
                TaxCategory: einvoice.CategoryStandard,
                TaxRate:     toDec("19.0"),
            },
            {
                Description: "Hosting Fee",
                Quantity:    toDec("1.0"),
                UnitCode:    "C62",
                UnitPrice:   toDec("50.00"),
                TaxCategory: einvoice.CategoryStandard,
                TaxRate:     toDec("19.0"),
            },
        },
        // Totals (net, VAT per rate, grand total) are computed from the lines.
    }

    // Serialize to XML (CII / XRechnung format)
    serializer := cii.NewSerializer()
    xmlBytes, err := serializer.Serialize(&invoice)
    if err != nil {
        log.Fatalf("Failed to generate XML: %v", err)
    }

    // Save as pure XRechnung XML
    os.WriteFile("xrechnung.xml", xmlBytes, 0644)

    // Create ZUGFeRD (Hybrid PDF)
    err = zugferd.EmbedXML("invoice.pdf", xmlBytes, "zugferd.pdf")
    if err != nil {
        log.Fatalf("Failed to embed XML into PDF: %v", err)
    }
    
    log.Println("Successfully generated E-Invoices!")
}
Differenzbesteuerung (§25a UStG)

For margin-scheme goods (used cars, art, antiques) VAT is not shown separately. Use category E with rate 0 and the matching VATEX exemption code; the library emits the exemption reason (BT-120/BT-121) and the mandatory legal note (BG-1):

einvoice.LineItem{
    Description:   "Gebrauchtwagen VW Golf",
    Quantity:      toDec("1"),
    UnitCode:      "C62",
    UnitPrice:     toDec("10000.00"),
    TaxCategory:   einvoice.CategoryExempt,      // "E"
    TaxRate:       toDec("0"),
    ExemptionCode: einvoice.VATExSecondHandGoods, // VATEX-EU-F → "Gebrauchtgegenstände/Sonderregelung"
}

Mixing rates on one invoice (e.g. a margin-scheme car plus a 19% delivery fee) is supported — each (category, rate) combination becomes its own VAT breakdown group.

Other categories work the same way: CategoryIntraCommunity (K, tax-free EU supply — a deliver-to address is added automatically) and CategoryReverseCharge (AE, §13b) with their matching VATEx* exemption codes.

Document-level discounts and surcharges go in Invoice.AllowanceCharges (AllowanceCharge, BG-20/21); they adjust the taxable basis of their VAT group.

Cancellation invoices (Stornorechnung) & credit notes

Invoice.TypeCode sets the document type (BT-3); empty defaults to 380 (commercial invoice). For a Stornorechnung use einvoice.TypeCorrectedInvoice (384) with negated quantities (never negative unit prices — BR-27):

invoice.TypeCode = einvoice.TypeCorrectedInvoice // "384"

einvoice.TypeCreditNote (381) is also available; note that CII sign conventions expect positive amounts on a 381 document.

CLI Usage

Generate XML
./einvoice -in invoice.json -out invoice
# Creates invoice.xml
Generate ZUGFeRD PDF
./einvoice -in invoice.json -pdf invoice.pdf -out invoice
# Creates invoice.pdf

Validation

Generated invoices are validated in CI against the official EN 16931 and XRechnung (KoSIT) schematron rules. To run it locally you need python3 with saxonche (an XSLT 2.0 engine):

pip install saxonche
bash scripts/validate.sh   # builds example/invoice.json → CII XML → validates

The script downloads and caches the official schematron and fails on any blocking (fatal/error) rule violation.

License

GNU AFFERO GENERAL PUBLIC LICENSE

Documentation

Overview

Package einvoice provides the domain model for German E-Invoices (E-Rechnung) compliant with EN 16931.

The Invoice type is the entry point. Serialize it to XRechnung 3.0 / CII XML with the cii package, and embed that XML into a PDF/A-3 (ZUGFeRD / Factur-X) with the zugferd package.

Index

Examples

Constants

View Source
const (
	// TypeCommercialInvoice is a regular invoice (default).
	TypeCommercialInvoice = "380"
	// TypeCreditNote is a credit note (Gutschrift). Note the CII sign
	// convention: amounts on a 381 document are positive.
	TypeCreditNote = "381"
	// TypeCorrectedInvoice is a corrected invoice (Rechnungskorrektur /
	// Stornorechnung) carrying negative amounts that reverse the original.
	TypeCorrectedInvoice = "384"
)

Document type codes (BT-3, UNTDID 1001 subset permitted by EN 16931).

Variables

This section is empty.

Functions

This section is empty.

Types

type AllowanceCharge added in v0.2.0

type AllowanceCharge struct {
	Amount      decimal.Decimal
	IsCharge    bool // false = allowance (reduces total), true = charge (adds)
	TaxCategory TaxCategory
	TaxRate     decimal.Decimal
	Reason      string
}

AllowanceCharge is a document-level allowance (discount) or charge (BG-20 / BG-21). Its TaxCategory and TaxRate must match the goods they apply to so the amount lands in the right VAT breakdown group. Reason is required by XRechnung.

type Contact

type Contact struct {
	Name  string
	Phone string
	Email string
}

type Invoice

type Invoice struct {
	Number string
	// TypeCode is the document type (BT-3). Empty defaults to
	// TypeCommercialInvoice ("380").
	TypeCode     string
	IssueDate    time.Time
	DueDate      time.Time
	DeliveryDate time.Time // BT-72 Leistungs-/Lieferdatum (UStG mandatory)
	// DeliveryCountryCode is the deliver-to country (BT-80). Mandatory for
	// intra-community supply (category K); defaults to the buyer's country.
	DeliveryCountryCode string
	Currency            string
	Note                string
	BuyerReference      string

	Seller           Party
	Buyer            Party
	Payment          Payment
	LineItems        []LineItem
	AllowanceCharges []AllowanceCharge // document-level allowances/charges (BG-20/21)
}
Example
package main

import (
	"fmt"

	einvoice "github.com/dotwavehq/go-einvoice"
	"github.com/dotwavehq/go-einvoice/cii"
	"github.com/shopspring/decimal"
)

func main() {
	inv := &einvoice.Invoice{
		Number:   "RE-2025-1001",
		Currency: "EUR",
		Seller:   einvoice.Party{Name: "Seller GmbH", CountryCode: "DE", VATID: "DE123456789"},
		Buyer:    einvoice.Party{Name: "Buyer AG", CountryCode: "DE"},
		LineItems: []einvoice.LineItem{{
			Description: "Consulting",
			Quantity:    decimal.NewFromInt(10),
			UnitCode:    "HUR",
			UnitPrice:   decimal.RequireFromString("100.00"),
			TaxCategory: einvoice.CategoryStandard,
			TaxRate:     decimal.NewFromInt(19),
		}},
	}

	xmlBytes, err := cii.NewSerializer().Serialize(inv)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(xmlBytes) > 0)
}
Output:
true

type InvoiceSerializer

type InvoiceSerializer interface {
	Serialize(invoice *Invoice) ([]byte, error)
}

InvoiceSerializer defines the contract for converting the domain model into a spec format.

type LineItem

type LineItem struct {
	Description string
	Quantity    decimal.Decimal
	UnitCode    string
	UnitPrice   decimal.Decimal

	// TaxCategory is the EN 16931 VAT category (BT-151). Empty defaults to
	// Standard ("S"). For Differenzbesteuerung (§25a UStG) use CategoryExempt
	// with TaxRate 0 and ExemptionCode VATExSecondHandGoods.
	TaxCategory TaxCategory
	TaxRate     decimal.Decimal

	// ExemptionCode (BT-121) and ExemptionReason (BT-120) are required for
	// non-standard categories (E, AE, K). If ExemptionReason is empty, the
	// default German text for a known ExemptionCode is used.
	ExemptionCode   VATExCode
	ExemptionReason string
}

type ParsedInvoice added in v0.5.0

type ParsedInvoice struct {
	Invoice

	Totals Totals
	// Taxes is the document-level VAT breakdown (BG-23).
	Taxes []TaxBreakdown
	// Format is the detected syntax: "cii" (ZUGFeRD / Factur-X / XRechnung
	// CII) or "ubl" (XRechnung UBL, Peppol BIS).
	Format string
	// Guideline is the specification identifier (BT-24): the CII guideline
	// URN or the UBL CustomizationID.
	Guideline string
}

ParsedInvoice is the result of reading an e-invoice XML. It carries the canonical Invoice plus the document-level monetary totals and the VAT breakdown as stated in the document — parsing never recomputes amounts, so a consumer can compare stated vs. derived values for validation.

func Parse added in v0.5.0

func Parse(data []byte) (*ParsedInvoice, error)

Parse reads an EN 16931 e-invoice XML in either CII or UBL syntax. It is lenient: fields absent from the document stay zero — callers that need guarantees should check the fields they rely on.

type Party

type Party struct {
	Name        string
	Street      string
	City        string
	PostalCode  string
	CountryCode string
	VATID       string
	Contact     *Contact

	// ElectronicAddress is the party's electronic address (BT-34 seller / BT-49
	// buyer), mandatory in XRechnung. ElectronicAddressScheme is its EAS code
	// (e.g. "EM" email, "9930" German VAT, "0204" Leitweg-ID); defaults to "EM".
	// If ElectronicAddress is empty, Contact.Email is used as an "EM" fallback.
	ElectronicAddress       string
	ElectronicAddressScheme string
}

type Payment

type Payment struct {
	IBAN             string
	BIC              string
	AccountHolder    string
	PaymentMeansCode string

	// AdditionalAccounts are further payee accounts. EN 16931 allows several
	// credit-transfer accounts (BG-17 is 0..n); each account is serialized as
	// its own SpecifiedTradeSettlementPaymentMeans with the same type code.
	AdditionalAccounts []PaymentAccount
}

type PaymentAccount added in v0.3.0

type PaymentAccount struct {
	IBAN          string
	BIC           string
	AccountHolder string
}

PaymentAccount is one additional payee bank account (BG-17 CREDIT TRANSFER).

type TaxBreakdown added in v0.5.0

type TaxBreakdown struct {
	CategoryCode string
	Rate         decimal.Decimal
	Basis        decimal.Decimal
	Amount       decimal.Decimal
}

TaxBreakdown is one VAT category subtotal (BG-23).

type TaxCategory added in v0.2.0

type TaxCategory string

TaxCategory is an EN 16931 / UNTDID 5305 VAT category code (BT-118 / BT-151).

const (
	CategoryStandard       TaxCategory = "S"  // Standard rate (19% / 7%)
	CategoryZero           TaxCategory = "Z"  // Zero rated goods
	CategoryExempt         TaxCategory = "E"  // Exempt from VAT (incl. §25a margin scheme)
	CategoryReverseCharge  TaxCategory = "AE" // VAT reverse charge (§13b UStG)
	CategoryIntraCommunity TaxCategory = "K"  // Intra-community supply
	CategoryExport         TaxCategory = "G"  // Free export item, tax not charged
	CategoryOutOfScope     TaxCategory = "O"  // Services outside scope of tax
)

type Totals added in v0.5.0

type Totals struct {
	LineTotal     decimal.Decimal // BT-106
	TaxBasisTotal decimal.Decimal // BT-109
	TaxTotal      decimal.Decimal // BT-110
	GrandTotal    decimal.Decimal // BT-112
	DuePayable    decimal.Decimal // BT-115
}

Totals are the document-level amounts (BG-22) as stated in the XML.

type VATExCode added in v0.2.0

type VATExCode string

VATExCode is an EN 16931 VAT exemption reason code (BT-121), from the CEF VATEX code list. Codes F/I/J cover the German Differenzbesteuerung variants.

const (
	VATExSecondHandGoods VATExCode = "VATEX-EU-F"  // Gebrauchtgegenstände (e.g. used cars)
	VATExWorksOfArt      VATExCode = "VATEX-EU-I"  // Kunstgegenstände
	VATExCollectors      VATExCode = "VATEX-EU-J"  // Sammlungsstücke und Antiquitäten
	VATExReverseCharge   VATExCode = "VATEX-EU-AE" // Steuerschuldnerschaft des Leistungsempfängers
	VATExIntraCommunity  VATExCode = "VATEX-EU-IC" // Innergemeinschaftliche Lieferung
)

func (VATExCode) DefaultReason added in v0.2.0

func (c VATExCode) DefaultReason() string

DefaultReason returns the standard German exemption text (BT-120) mandated for a known VATEX code, or "" if the code is unknown.

Directories

Path Synopsis
cmd
einvoice command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL