einvoice

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: AGPL-3.0 Imports: 2 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.

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.

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

This section is empty.

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
	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 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 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 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