UBL (Universal Business Language) document generation and parsing for Elixir.
This library provides:
- Peppol BIS Billing 3.0 compliant invoice, credit note, and application response generation
- SBDH (Standard Business Document Header) support for Peppol network transmission
- Fast SAX-based XML parsing using Saxy (1.4-4x faster than xmerl)
- Full round-trip support (parse → generate → parse without data loss)
Quick Start
Generating a UBL Invoice
document_data = %{
type: :invoice,
number: "F2024001",
date: ~D[2024-01-15],
expires: ~D[2024-02-14],
supplier: %{
endpoint_id: "0797948229",
scheme: "0208",
name: "Company Name",
street: "Street 40",
city: "City",
zipcode: "2180",
country: "BE",
vat: "BE0797948229",
email: "invoice@company.com"
},
customer: %{
endpoint_id: "0012345625",
scheme: "0208",
name: "Customer Name",
vat: "BE0012345625",
street: "Customer Street",
housenumber: "10",
city: "Brussels",
zipcode: "1000",
country: "BE"
},
details: [
%{
name: "Service",
quantity: Decimal.new("1.00"),
price: Decimal.new("100.00"),
vat: Decimal.new("21.00"),
discount: Decimal.new("0.00")
}
]
}
xml = UblEx.generate(document_data)Parsing a UBL Document
{:ok, parsed} = UblEx.parse(xml_content)
# Document type is in the data
case parsed.type do
:invoice -> handle_invoice(parsed)
:credit -> handle_credit_note(parsed)
:application_response -> handle_response(parsed)
endTax Categories
Line items support all Peppol BIS 3.0 tax categories via the optional tax_category field:
| Atom | Peppol Code | Use Case |
|---|---|---|
:standard | S | Standard rated VAT (default for 6/12/21%) |
:zero_rated | Z | Zero rated goods (default for 0% VAT) |
:exempt | E | Exempt from tax |
:reverse_charge | AE | Domestic reverse charge |
:intra_community | K | EU cross-border B2B (intra-community supply) |
:export | G | Export outside EU |
:outside_scope | O | Services outside scope of tax |
If tax_category is not specified, it defaults to :standard for non-zero VAT
and :zero_rated for 0% VAT.
Tax Exemption Fields
According to Peppol BIS 3.0 validation rules (BR-O-11 through BR-O-14), when using
tax categories :exempt, :export, :intra_community, or :reverse_charge, you
must provide tax exemption information via these fields:
tax_exemption_reason_code- A VATEX code (e.g., "vatex-eu-ic", "vatex-eu-ae")tax_exemption_reason- A human-readable explanation (e.g., "Vrijgestelde intracommunautaire levering - Art. 39bis WBTW")
The tax_exemption_reason_code follows the format vatex-{country}-{code}:
vatex-eu-ic- Intra-community supplyvatex-eu-ae- Reverse charge / Autoliquidationvatex-eu-g- Export outside EUvatex-be-xxx- Belgian-specific codes
Example with Tax Categories and Exemption Fields
details: [
%{
name: "Standard Service",
quantity: Decimal.new("1.00"),
price: Decimal.new("100.00"),
vat: Decimal.new("21.00"),
discount: Decimal.new("0.00")
# tax_category defaults to :standard
},
%{
name: "EU Cross-Border Service",
quantity: Decimal.new("1.00"),
price: Decimal.new("500.00"),
vat: Decimal.new("0.00"),
discount: Decimal.new("0.00"),
tax_category: :intra_community,
tax_exemption_reason_code: "vatex-eu-ic",
tax_exemption_reason: "Vrijgestelde intracommunautaire levering - Art. 39bis WBTW"
}
]Usage Pattern
Parse documents and handle them in your own code:
{:ok, parsed} = UblEx.parse(xml)
MyApp.save_invoice(parsed)
MyApp.send_notification(parsed)
Summary
Functions
Generate a Peppol-compliant UBL document based on the type field.
Generate a UBL document wrapped in SBDH (Standard Business Document Header).
Parse UBL XML with automatic schema detection.
Strip the StandardBusinessDocument/StandardBusinessDocumentHeader wrapper from UBL XML.
Functions
Generate a Peppol-compliant UBL document based on the type field.
Routes to the appropriate generator based on document_data.type:
:invoice→ Invoice generator:credit→ CreditNote generator:application_response→ ApplicationResponse generator
Examples
# Parse and regenerate
{:ok, parsed} = UblEx.parse(xml)
regenerated_xml = UblEx.generate(parsed)
# Generate invoice
document_data = %{type: :invoice, number: "F001", ...}
xml = UblEx.generate(document_data)
# Generate application response
response_data = %{type: :application_response, id: "RESP-001", ...}
xml = UblEx.generate(response_data)
Generate a UBL document wrapped in SBDH (Standard Business Document Header).
SBDH is used in Peppol networks for routing and identification. This function generates the standard UBL document and wraps it with the SBDH header.
Example
document_data = %{type: :invoice, number: "F001", ...}
sbdh_xml = UblEx.generate_with_sbdh(document_data)
Parse UBL XML with automatic schema detection.
Returns {:ok, parsed_data} or {:error, reason}.
The document type is available in parsed_data.type (:invoice, :credit, or :application_response).
Example
{:ok, parsed} = UblEx.parse(xml_content)
IO.inspect(parsed.type) # :invoice, :credit, or :application_response
Strip the StandardBusinessDocument/StandardBusinessDocumentHeader wrapper from UBL XML.
Some accounting software cannot process documents with SBDH wrappers. This function extracts the inner UBL document (Invoice, CreditNote, or ApplicationResponse).
If the XML is not wrapped in an SBDH, it is returned unchanged.
Example
{:ok, sbdh_xml} = File.read("invoice_with_sbdh.xml")
plain_xml = UblEx.strip_sbdh(sbdh_xml)