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

Invoice Laravel Package

webchemistry/invoice

PHP invoice library for generating invoices with PDF output, QR payment codes, and configurable invoice data. Simplifies creating, rendering, and exporting invoices in PHP applications.

Deep Wiki
Context7

Getting Started

Install via Composer; the package pulls in mPDF for rendering, so you get a %PDF binary out of the box with no headless browser:

composer require webchemistry/invoice

Minimal first invoice — the three concepts you need are party (who), item (what), and invoice (the document):

use WebChemistry\Invoice\Invoice;
use WebChemistry\Invoice\InvoiceParty;
use WebChemistry\Invoice\InvoiceItem;

$invoice = new Invoice();

$invoice->setNumber('2026-0001');
$invoice->setCompany(new InvoiceParty(
    'Acme s.r.o.', 'Hlavní 1', 'Praha', '110 00', 'CZ', '12345678', 'CZ12345678'
));
$invoice->setCustomer(new InvoiceParty(
    'Client a.s.', 'Vedlejší 2', 'Brno', '602 00', 'CZ', '87654321', 'CZ87654321'
));

$invoice->addItem(new InvoiceItem('Consulting', 10, 1500.00, 21)); // qty, unit price, VAT %
$invoice->addItem(new InvoiceItem('Hosting',     1,  299.00, 21));

$invoice->setDateOfIssue(new \DateTimeImmutable());
$invoice->setDateOfDue(new \DateTimeImmutable('+14 days'));

$pdf = $invoice->generate(); // string
file_put_contents(storage_path('invoices/2026-0001.pdf'), $pdf);

Where to look first: the examples/ directory in the repo (if present) is the fastest source of truth for the exact constructor/method signatures for your installed version — the API is small but has shifted between minor releases. Then read src/Invoice.php and the bundled template under src/templates/ to understand what data the PDF actually renders. If you're on Nette, check the DI extension (usually WebChemistry\Invoice\DI\InvoiceExtension) so you don't hand-wire objects.

First real use case: generating a downloadable invoice at the end of a checkout or from an admin "issue invoice" button. Don't try to email it on day one — get the file on disk, eyeball it, then automate storage and delivery.

Implementation Patterns

Wrap the package in your own service

Never scatter new Invoice() across controllers. Build one factory that takes your domain objects and returns a configured invoice:

final class InvoiceFactory
{
    public function __construct(
        private string $templatePath,
        private string $sellerIco,
        // ... company config
    ) {}

    public function fromOrder(Order $order): Invoice
    {
        $invoice = new Invoice();
        $invoice->setTemplate($this->templatePath);
        $invoice->setNumber($order->invoice_number);
        $invoice->setCompany($this->sellerParty());
        $invoice->setCustomer($this->customerParty($order->customer));

        foreach ($order->items as $line) {
            $invoice->addItem(new InvoiceItem(
                $line->description,
                $line->quantity,
                $line->unit_price, // keep as decimal, not cents, here
                $line->vat_rate,
            ));
        }

        return $invoice;
    }
}

This is the seam that makes the package testable and swappable. Your controllers depend on InvoiceFactory, not on WebChemistry\Invoice\Invoice.

Persist the PDF, don't regenerate on demand

Invoices are legal documents. Generate once, store the bytes (local disk, S3), and record the path plus a hash on the order row:

$pdf = $this->invoiceFactory->fromOrder($order)->generate();
Storage::disk('invoices')->put($path = "{$order->invoice_number}.pdf", $pdf);
$order->update(['invoice_path' => $path, 'invoice_generated_at' => now()]);

Subsequent downloads stream the stored file. This avoids re-rendering cost, protects against the package changing output after an upgrade, and gives you a deterministic artifact for audits.

Allocate the invoice number inside a transaction

The single biggest correctness risk is duplicate or skipped invoice numbers. Reserve the number before rendering, under a lock:

DB::transaction(function () use ($order) {
    $seq = InvoiceSequence::lockForUpdate()->first();
    $order->invoice_number = sprintf('%s-%04d', $seq->year, ++$seq->last);
    $seq->save();
    $order->save();
});
// render after the transaction commits

Then render in a separate step (or a queued job) so a PDF failure doesn't roll back the number — you want a number reserved and a retryable render, not a gap you can't explain to an auditor.

Queue generation for anything bulk

Generating hundreds of PDFs inline will time out. Push a GenerateInvoicePdf job per order onto a queue, with tries = 3 and a unique job key on the invoice number so retries don't double-render. mPDF is CPU-bound and single-threaded; horizontal workers scale it fine.

Customize via the template, not by hacking the package

The clean extension point is the HTML template that mPDF renders. Copy the bundled template into resources/views/invoices/default.html, point setTemplate() at it, and restyle with CSS that mPDF understands (tables, @page, inline-ish CSS — no flexbox). Keep your branding there, keep your logic in InvoiceFactory. If you subclass Invoice you inherit maintenance debt on upgrade; template overrides survive minor bumps.

Test the contract, not the pixels

Assert the factory produces the right totals and that generate() returns a non-empty string starting with %PDF:

$pdf = $this->invoiceFactory->fromOrder($order)->generate();
$this->assertStringStartsWith('%PDF', $pdf);
$this->assertGreaterThan(1000, strlen($pdf));

If the package exposes the intermediate HTML, snapshot that — it's diffable and stable, unlike PDF bytes which change with every mPDF bump.

Gotchas and Tips

  • Floats for money will bite you. The package accepts decimal prices; do your arithmetic in integer cents, convert at the boundary, and round explicitly with round($v, 2, PHP_ROUND_HALF_UP) before handing values to InvoiceItem. A cent of drift on a PDF is a support ticket.
  • VAT is per-item, and the package will sum it for you — verify the summary. If your business mixes rates (e.g. 21 % and 12 % in CZ), confirm the tax recap groups correctly. Don't assume; open a generated PDF with mixed rates on day one and check the totals table by hand.
  • Numbering is not the package's job. There is no sequence generator, no uniqueness guarantee, no locking. That's entirely on your application layer — see the transaction pattern above. This is the most common production incident with invoice libraries.
  • Dates and timezones. Store DateTimeImmutable in your app timezone; mPDF renders whatever you pass. Set issue/due/tax dates explicitly rather than relying on defaults, and be deliberate about the tax date (DUZP) if you're in the EU — it's separate from the issue date in many regimes.
  • mPDF memory and fonts. Diacritics (ě, š, č, ř, ž) need a font that covers them. If characters render as boxes, set default_font in mPDF config or embed a Unicode font (DejaVu Sans is the safe default). Bump memory_limit if you render large item tables — mPDF is memory-hungry.
  • Template path resolution. setTemplate() wants a filesystem path, not a Laravel view name. Resolve it with resource_path() / base_path() and fail loudly if the file is missing — the error message from mPDF for a missing template is unhelpfully vague.
  • generate() returns a string, not a response. Don't echo it. Wrap it in your framework's download/stream response (response($pdf)->header('Content-Type', 'application/pdf')) or write it to storage.
  • Check the installed version's signatures. Constructor argument order (qty, price, VAT) and setter names have varied across releases. Pin a version in composer.json, and if you upgrade, run your invoice tests plus a visual diff of one sample PDF.
  • Extension points, ranked by safety: (1) template override, (2) InvoiceFactory wrapper, (3) subclassing Invoice, (4) forking the package. Stay in (1) and (2) unless you hit a wall; the package is small (≈98 stars, single maintainer) so upstream fixes may be slow — vendor a fork only if you truly need it.
  • Keep a golden-sample test. Store one generated PDF per quarter as a fixture and diff page count + total on render. It catches template regressions and silent dependency changes that unit tests miss.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource