PHPackages                             vimatech/laravel-einvoicing - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [PDF &amp; Document Generation](/categories/documents)
4. /
5. vimatech/laravel-einvoicing

ActiveLibrary[PDF &amp; Document Generation](/categories/documents)

vimatech/laravel-einvoicing
===========================

Generate compliant structured e-invoices (Peppol BIS 3.0 UBL, EN 16931 CII) natively and dispatch them through pluggable networks. Zero third-party runtime dependencies.

00PHPCI passing

Since Jun 26Pushed todayCompare

[ Source](https://github.com/vimatech-io/laravel-einvoicing)[ Packagist](https://packagist.org/packages/vimatech/laravel-einvoicing)[ RSS](/packages/vimatech-laravel-einvoicing/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel E-Invoicing
===================

[](#laravel-e-invoicing)

[![CI](https://github.com/vimatech-io/laravel-einvoicing/actions/workflows/ci.yml/badge.svg)](https://github.com/vimatech-io/laravel-einvoicing/actions/workflows/ci.yml)[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](LICENSE)

Generate compliant structured e-invoices **natively** and dispatch them through pluggable networks (Peppol access points, French PDPs), with per-country routing — for Laravel 11, 12 and 13.

> **Zero third-party runtime dependencies.** Every document is built with PHP's own `ext-dom`; every network call uses Laravel's own HTTP client. No `horstoeko/zugferd`, no UBL libraries, no PDF libraries. This is a deliberate security &amp; maintenance policy.

Features
--------

[](#features)

- **Native generators** — Peppol BIS Billing 3.0 (UBL invoice + credit note) and EN 16931 CII, emitted directly with `DOMDocument`. Factur-X (PDF/A-3) is stubbed for a later isolated module.
- **Neutral domain model** — a single `CanonicalInvoice` DTO; no format or vendor concept ever leaks into your application.
- **Pluggable networks** — `PeppolDriver`, `FrPdpDriver`, `NullDriver`, `FakeDriver`, plus your own.
- **Per-country routing** — map destination countries to networks, with a fallback and a per-tenant override hook.
- **Native validation** — mandatory-field and arithmetic checks (EN 16931 subset) fail fast with actionable messages before anything is rendered or transmitted.
- **Lifecycle events** — `EInvoiceGenerated`, `EInvoiceDispatched`, `EInvoiceDelivered`, `EInvoiceRejected`, `EInvoiceReceived`.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11, 12 or 13
- Extensions: `ext-dom`, `ext-xmlwriter`, `ext-libxml`, `ext-mbstring`

Installation
------------

[](#installation)

```
composer require vimatech/laravel-einvoicing
```

Publish the config (optional):

```
php artisan vendor:publish --tag=einvoicing-config
```

Quick start
-----------

[](#quick-start)

### 1. Build a canonical invoice

[](#1-build-a-canonical-invoice)

The `CanonicalInvoice` is the only model you ever construct. It is format- and network-agnostic.

```
use Vimatech\EInvoicing\Dtos\{CanonicalInvoice, Party, LineItem, TaxBreakdown};

$invoice = new CanonicalInvoice(
    number: 'INV-2024-0001',
    issueDate: new DateTimeImmutable('2024-01-15'),
    currency: 'EUR',
    seller: new Party(
        name: 'Acme Trading Ltd.',
        countryCode: 'BE',
        endpointId: '0208:0123456789',   // Peppol electronic address (BT-34)
        endpointScheme: '0208',          // EAS scheme id
        vatId: 'BE0123456789',
        legalRegistrationId: '0123456789',
        legalRegistrationScheme: '0208',
        street: 'Main street 1',
        city: 'Brussels',
        postalZone: '1050',
    ),
    buyer: new Party(
        name: 'Globex NV',
        countryCode: 'BE',
        endpointId: '0208:9876543210',
        endpointScheme: '0208',
        vatId: 'BE9876543210',
        street: 'Market square 9',
        city: 'Antwerp',
        postalZone: '2000',
    ),
    lines: [
        new LineItem(
            id: '1',
            name: 'Laptop computer',
            quantity: 5.0,
            netPrice: 200.0,
            lineExtensionAmount: 1000.0,
            taxCategory: 'S',
            taxPercent: 21.0,
        ),
    ],
    taxBreakdowns: [
        new TaxBreakdown(category: 'S', percent: 21.0, taxableAmount: 1000.0, taxAmount: 210.0),
    ],
    dueDate: new DateTimeImmutable('2024-02-14'),
    buyerReference: 'PO-98765',
);
```

> Document totals (line extension, tax exclusive/inclusive, payable) are derived from the lines and the VAT breakdown — you do not pass them in.

### 2. Generate a UBL (Peppol BIS 3.0) document

[](#2-generate-a-ubl-peppol-bis-30-document)

```
use Vimatech\EInvoicing\Facades\EInvoice;
use Vimatech\EInvoicing\Enums\Format;

$document = EInvoice::format(Format::Ubl)->generate($invoice);

$document->contents;        // the XML string
$document->mimeType;        // application/xml
$document->profile;         // urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0
$document->save('/path/to/INV-2024-0001.xml');
```

A **credit note** is the same call with `typeCode: CanonicalInvoice::TYPE_CREDIT_NOTE` — the generator switches to the `CreditNote` root and `CreditedQuantity` automatically. Generate **CII**with `Format::Cii`.

If a mandatory field is missing or the arithmetic does not balance, generation throws `InvalidInvoice`, which carries the full list of violations:

```
use Vimatech\EInvoicing\Exceptions\InvalidInvoice;

try {
    EInvoice::format(Format::Ubl)->generate($invoice);
} catch (InvalidInvoice $e) {
    $e->violations(); // ['BT-1: invoice number is required', ...]
}
```

### 3. Send through a network

[](#3-send-through-a-network)

`send()` renders the document, routes it by the **buyer's country**, transmits it, and fires the lifecycle events:

```
$result = EInvoice::send($invoice); // Format defaults to config('einvoicing.default_format')

$result->status;       // LifecycleStatus::Delivered | Submitted | Rejected | ...
$result->messageId;    // poll later with fetchStatus()
$result->successful(); // bool
```

Force a format or a specific network:

```
EInvoice::send($invoice, Format::Ubl, networkKey: 'peppol');
```

Resolve a network yourself:

```
EInvoice::route('BE');        // network responsible for Belgium
EInvoice::network('peppol');  // a network by key

$status = EInvoice::network('peppol')->fetchStatus($result->messageId);
```

### 4. Receive inbound documents

[](#4-receive-inbound-documents)

```
foreach (EInvoice::receive('peppol') as $inbound) {
    $inbound->contents;  // raw XML
    $inbound->senderId;  // sender electronic address
}
// each inbound document also fires an EInvoiceReceived event
```

Configuration
-------------

[](#configuration)

`config/einvoicing.php` declares the available **networks**, the **country → network** routing table, and the default format. Credentials come from the environment.

```
'networks' => [
    'peppol' => [
        'driver' => 'peppol',
        'base_url' => env('PEPPOL_BASE_URL'),
        'token' => env('PEPPOL_API_TOKEN'),
        'paths' => ['send' => '/documents', 'status' => '/documents/{id}/status', 'inbound' => '/inbound'],
        'status_map' => [],   // map partner status strings -> LifecycleStatus values
    ],
    'fr_pdp' => ['driver' => 'fr_pdp', 'base_url' => env('FR_PDP_BASE_URL'), 'token' => env('FR_PDP_API_TOKEN')],
    'sandbox' => ['driver' => 'null'],
],

'routing' => [
    'FR' => 'fr_pdp',
    'BE' => 'peppol',
    'NL' => 'peppol',
    // ...
],

'fallback' => env('EINVOICING_FALLBACK_NETWORK'), // null => unmatched countries throw
```

### Adapting a built-in driver to your partner

[](#adapting-a-built-in-driver-to-your-partner)

`PeppolDriver` and `FrPdpDriver` speak a small, neutral REST shape. Point them at your access point / PDP by overriding `paths` and, where the vocabulary differs, `status_map` — no code change:

```
'peppol' => [
    'driver' => 'peppol',
    'base_url' => env('PEPPOL_BASE_URL'),
    'token' => env('PEPPOL_API_TOKEN'),
    'paths' => [
        'send' => '/v2/outbound',
        'status' => '/v2/messages/{id}',
        'inbound' => '/v2/inbound',
    ],
    'status_map' => [
        'in_progress' => 'in_transit',
        'done' => 'delivered',
    ],
],
```

Adding a country
----------------

[](#adding-a-country)

Add a row to the `routing` map pointing at any configured network key:

```
'routing' => [
    'ES' => 'peppol',
    'IT' => 'peppol',
],
```

Unmatched countries throw `UnsupportedCountry` unless a `fallback` network is set.

### Per-tenant routing override

[](#per-tenant-routing-override)

Register a resolver (e.g. in a service provider) to override routing per tenant or per invoice. Returning a network key wins over the static map; returning `null` defers to it:

```
use Vimatech\EInvoicing\Facades\EInvoice;

EInvoice::router()->overrideUsing(function (string $country, ?CanonicalInvoice $invoice) {
    return tenant()->prefersDirectPeppol() ? 'peppol' : null;
});
```

Adding a driver
---------------

[](#adding-a-driver)

Implement `EInvoiceNetwork` (or extend `AbstractHttpDriver` for a REST partner) — keep every vendor concept inside the driver:

```
namespace App\EInvoicing;

use Vimatech\EInvoicing\Contracts\EInvoiceNetwork;
use Vimatech\EInvoicing\Dtos\{CanonicalInvoice, DispatchResult, GeneratedDocument, NetworkCapabilities};
use Vimatech\EInvoicing\Enums\{Format, LifecycleStatus};

final class AcmeDriver implements EInvoiceNetwork
{
    public function __construct(private array $config, private string $key) {}

    public function key(): string { return $this->key; }

    public function send(GeneratedDocument $document, CanonicalInvoice $invoice): DispatchResult
    {
        // ... call your partner, then normalise the response ...
        return new DispatchResult(LifecycleStatus::Submitted, $this->key, messageId: '...');
    }

    public function fetchStatus(string $messageId): DispatchResult { /* ... */ }
    public function receive(): array { return []; }
    public function capabilities(): NetworkCapabilities
    {
        return new NetworkCapabilities($this->key, formats: [Format::Ubl]);
    }
}
```

Reference it by class in config (it is resolved from the container):

```
'networks' => [
    'acme' => ['driver' => App\EInvoicing\AcmeDriver::class, /* ... */],
],
```

Or register a factory at runtime:

```
app(\Vimatech\EInvoicing\Networks\NetworkManager::class)
    ->extend('acme', fn (array $config, string $key) => new App\EInvoicing\AcmeDriver($config, $key));
```

Testing with the FakeDriver
---------------------------

[](#testing-with-the-fakedriver)

A dependency-free `FakeDriver` is shipped for **your** test suite. Swap any network for it and assert against what was sent — no HTTP, no credentials:

```
use Vimatech\EInvoicing\Facades\EInvoice;
use Vimatech\EInvoicing\Enums\LifecycleStatus;

it('sends the invoice to Peppol', function () {
    $fake = EInvoice::fake('peppol');

    EInvoice::send($invoice);

    $fake->assertSent(fn ($invoice) => $invoice->number === 'INV-2024-0001');
    $fake->assertSentCount(1);
    expect($fake->lastSent()->number)->toBe('INV-2024-0001');
});

it('handles a rejection', function () {
    EInvoice::fake('peppol')->alwaysReturn(LifecycleStatus::Rejected);

    expect(EInvoice::send($invoice)->rejected())->toBeTrue();
});
```

`FakeDriver` also supports `pushInbound()` for `receive()` flows, the inspection API (`sent()`, `sentCount()`, `hasSent()`, `lastSent()`) and the `assert*` helpers.

Conformance &amp; testing notes
-------------------------------

[](#conformance--testing-notes)

- **Profiles emitted**: Peppol BIS Billing 3.0 — `CustomizationID``urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0`, `ProfileID` `urn:fdc:peppol.eu:2017:poacc:billing:01:1.0`. CII carries the EN 16931 guideline `urn:cen.eu:en16931:2017`.
- **Scope**: only the subset of business terms required for the supported fields is emitted, in the UBL 2.1 / CII sequence order. Document-level allowances/charges and multi-currency VAT accounting are intentionally out of scope for this release.
- **Golden files**: UBL output is asserted byte-for-byte against committed golden samples (`tests/fixtures/peppol/`) shaped after the official Peppol BIS 3.0 examples, plus structural XPath assertions for identifiers and balancing totals.
- **Recommended external validation**: before production, run generated XML through the official [Peppol BIS / EN 16931 validator](https://ecosio.com/en/peppol-and-xml-document-validator/) or the CEN Schematron. Native validation here is a fast pre-flight, not a substitute for the Schematron.

Run the suite:

```
composer test      # Pest + orchestra/testbench
composer analyse   # PHPStan level max
composer lint      # Laravel Pint
```

Architecture
------------

[](#architecture)

```
CanonicalInvoice ──► FormatGenerator ──► GeneratedDocument
       │              (Ubl / Cii / FacturX)
       │
       └──► EInvoiceRouter ──► EInvoiceNetwork ──► DispatchResult
            (by country)       (Peppol / FrPdp / Null / Fake / yours)

```

- `Dtos/` — readonly value objects (the neutral model).
- `Formats/` — native `DOMDocument` generators + validation.
- `Networks/` — drivers + the config-driven `NetworkManager`.
- `Routing/` — `EInvoiceRouter` (country map + fallback + override hook).
- `Events/`, `Exceptions/`, `Facades/` — the glue.

Contributing
------------

[](#contributing)

Contributions are welcome.

Please ensure:

- Tests pass (`composer test`)
- PHPStan passes (`composer analyse`)
- Code style is formatted with Pint (`composer format`)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review our [Security Policy](SECURITY.md) for reporting vulnerabilities.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

Credits
-------

[](#credits)

Built and maintained by [Vimatech](https://vimatech.io). Created by [Adel Zemzemi](https://github.com/adelzemzemi).

###  Health Score

20

—

LowBetter than 13% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/3283664f06a0db1bfdbf282b2691363365c2f73569bcd99d63f5aaa52900ff55?d=identicon)[adelzemzemi](/maintainers/adelzemzemi)

---

Top Contributors

[![adelzemzemi](https://avatars.githubusercontent.com/u/272534830?v=4)](https://github.com/adelzemzemi "adelzemzemi (15 commits)")

### Embed Badge

![Health badge](/badges/vimatech-laravel-einvoicing/health.svg)

```
[![Health](https://phpackages.com/badges/vimatech-laravel-einvoicing/health.svg)](https://phpackages.com/packages/vimatech-laravel-einvoicing)
```

###  Alternatives

[42coders/document-templates

Document template management package.

19940.0k](/packages/42coders-document-templates)[qipsius/tcpdf-bundle

A bundle to easily integrate TCPDF into Symfony

23749.5k](/packages/qipsius-tcpdf-bundle)[macopedia/magmi2

Magento Mass Importer 'Magmi' for Magento 2

11615.7k](/packages/macopedia-magmi2)[tarfin-labs/easy-pdf

Makes pdf processing easy.

1719.4k](/packages/tarfin-labs-easy-pdf)[ikkez/f3-sheet

Some Excel and CSV utilities for PHP Fat-Free Framework

1414.8k](/packages/ikkez-f3-sheet)[nilgems/laravel-textract

A Laravel package to extract text from files like DOC, XL, Image, Pdf and more. I've developed this package by inspiring "npm textract".

195.7k](/packages/nilgems-laravel-textract)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
