Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ubl Invoice Laravel Package

cleverit/ubl_invoice

A Laravel/PHP package for building UBL invoice XML documents. cleverit/ubl_invoice provides an object-oriented API to assemble invoice data and output standards-compliant UBL XML for electronic invoicing workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is unmaintained (last release Feb 2019, no public repository linked), so treat this as a vendor-locked dependency to isolate and wrap, not a foundation to build on. Install it and confirm what it actually pulls in before writing application code.

composer require cleverit/ubl_invoice
composer show cleverit/ubl_invoice      # inspect require constraints + autoload
php artisan vendor:publish --provider="CleverIt\UBLInvoice\UBLInvoiceServiceProvider"

Look in this order:

  1. composer.json — PHP and illuminate/* constraints. If it caps laravel/framework below ^8, you cannot install it on Laravel 9+ without a fork or --ignore-platform-reqs (the latter is a stopgap, not a deployment answer).
  2. config/ubl_invoice.php (after publishing) — seller details, currency defaults, XML namespace/prefix options.
  3. src/ — the generator entry point (likely a facade or a bound service singleton). Read the constructor and any toXml()/save() method; that is your entire usable API surface.
  4. tests/ and examples/ if shipped — the fastest way to learn the expected input shape is usually a fixture invoice.

First use case: generate one valid UBL 2.1 invoice for a single hardcoded buyer/seller and one line item, write it to storage/app/invoices/, and validate it against the UBL XSD with xmllint. Do this before wiring anything to Eloquent — you want to know the package produces schema-valid output at all before debugging your own data mapping on top of it.

$invoice = app(UBLInvoice::class) // or the facade from the README
    ->setSeller($sellerData)
    ->setBuyer($buyerData)
    ->addLine($lineData);

Storage::put("invoices/{$number}.xml", $invoice->toXml());

Implementation Patterns

Wrap it, don't call it. The highest-value pattern with an abandoned package is a one-class seam:

interface InvoiceXmlGenerator { public function generate(InvoiceData $data): string; }

final class UblGenerator implements InvoiceXmlGenerator
{
    public function generate(InvoiceData $data): string { /* package calls live only here */ }
}

Everything else in your app depends on InvoiceXmlGenerator. When you eventually migrate to easybill/ubl-invoice, horstoeko/orderx, or a hand-rolled builder, one file changes.

DTO → mapper → generator. Keep your domain models clean. Have a dedicated InvoiceData value object (seller, buyer, lines, tax breakdown, totals) and a single OrderToInvoiceData mapper per source model. This is where 90% of your maintenance cost lands, so make it explicit and unit-testable rather than sprinkling array assembly into controllers.

Persist the XML, not just the result. Invoices are legal documents and must be immutable. Store the generated file with a content hash and the metadata you'll need to prove it:

$xml = $generator->generate($data);
$hash = hash('sha256', $xml);
Invoice::create([
    'number' => $number, 'buyer_vat' => $data->buyerVat,
    'total' => $data->total, 'xml_path' => $path, 'sha256' => $hash,
    'generated_at' => now(),
]);
Storage::disk('invoices')->put($path, $xml); // write-once

Once sent, never regenerate. Re-rendering from mutable Eloquent rows is how invoice numbers and totals drift from what the customer received.

Trigger from domain events. Bind generation to OrderPaid / InvoiceRequested via a queued listener, not to a controller. Bulk re-issues, month-end runs, and retries all become trivial. Use a per-entity sequence number (legal requirement), not id.

Validate before transport. The package only produces XML — it does not deliver. Validate with the UBL 2.1 XSD, then hand off to a Peppol Access Point or SDI connector:

$dom = new DOMDocument();
$dom->loadXML($xml);
$dom->schemaValidate(storage_path('schemas/UBL-Invoice-2.1.xsd')); // throws on failure

Golden-file tests. Snapshot the generated XML with frozen dates, IDs, and seller config. Normalize whitespace and XML declaration before asserting, and store fixtures as .xml files rather than inline strings so diffs are reviewable.

$this->assertXmlStringEqualsXmlString(
    file_get_contents(base_path('tests/fixtures/invoice-basic.xml')),
    $generator->generate($data)
);

Multi-tenant seller data. Put seller identity in a SellerProfile model or config keyed per tenant, not in the published config file — config is cached (config:cache) and won't survive runtime tenant switching.

Gotchas and Tips

  • Laravel/PHP compatibility is the first blocker. A 2019 package predates PHP 8.1 typed properties, enum handling, and illuminate/support ^9/^10/^11. Check composer why-not laravel/framework early. Realistic options: (a) fork, bump constraints, run tests, (b) pin your app to an old Laravel in a dedicated microservice, (c) replace it. Don't discover this at deploy time.
  • XML external entities / XXE. If any package code uses DOMDocument::loadXML or SimpleXML on incoming XML, ensure libxml_disable_entity_loader(true) semantics (PHP 8 default, but audit) and never parse untrusted XML with the package's helpers.
  • Element ordering is schema-enforced. UBL 2.1 is a sequence-based schema: cbc:ID must precede cbc:IssueDate, tax totals must come before cac:InvoiceLine, etc. Array-to-XML builders that emit keys in insertion order will fail validation. If you're injecting extra fields, test against the XSD, not just by eyeballing.
  • Decimals as strings. Use bcmul/string formatting with the mandated number of decimal places. Floats produce totals that fail Peppol's rounding rules, especially with VAT category codes S, Z, E, and AE, and with per-line vs per-document rounding differences.
  • Tax categories and exemptions. Zero-rated and exempt lines need a cbc:TaxExemptionReason and the right cbc:ID code; omitting it is the most common rejection reason.
  • Namespace prefixes. The schema doesn't care whether you use cac:/cbc: prefixes, but many validators and downstream parsers do. Keep the package's default consistent across all documents, and pin it in config.
  • IDs and timestamps. Invoice number series must be gap-free and per-legal-entity. Don't derive from id. Generate cbc:UUID and cbc:IssueDate deterministically in tests, or your snapshots break hourly.
  • Encoding. Assert UTF-8 without BOM; DOMDocument::$encoding = 'UTF-8' and no leading whitespace in the template.
  • Debugging loop: dump the raw XML to disk → xmllint --noout --schema UBL-Invoice-2.1.xsd invoice.xml → diff against a known-good sample from your access point → only then inspect the package call. The error messages will point at the schema line, which is usually your mapper, not the package.
  • Extension points are thin. With no maintained repo, expect no hooks or events. Your seam is the wrapper class and, if strictly necessary, a composer patch (cweagans/composer-patches) rather than editing vendor/.
  • Exit plan. Budget for replacement from day one. The wrapper interface, the stored XML corpus, and the golden-file tests are exactly the assets that make swapping the generator a days-long task instead of a rewrite.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr