vimatech/laravel-einvoicing 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

vimatech/laravel-einvoicing

Composer 安装命令:

composer require 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.

README 文档

README

CI 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 & maintenance policy.

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 networksPeppolDriver, 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 eventsEInvoiceGenerated, EInvoiceDispatched, EInvoiceDelivered, EInvoiceRejected, EInvoiceReceived.

Requirements

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

Installation

composer require vimatech/laravel-einvoicing

Publish the config (optional):

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

Quick start

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

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

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

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

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

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

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

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

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

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

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

Contributions are welcome.

Please ensure:

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

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our Security Policy for reporting vulnerabilities.

License

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

Credits

Built and maintained by Vimatech. Created by Adel Zemzemi.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-26

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固