Skip to content

Invoices ​

PDF invoices and packing slips for the shop's orders: numbered, attached to the shop's emails, downloaded in the admin and the customer's account.

kitloom/invoices · admins: kitloom/invoices-filament, kitloom/invoices-nova

Video lessons ​

Recorded in the Filament admin; the Nova admin works the same way.

Invoices on an order

PDF invoices and packing slips for the shop's orders. An order gets its invoice number when the first email you chose goes out with the PDF, when you open the invoice in the admin, when the customer downloads it from their account — or as soon as the order reaches a status you chose. The admin is kitloom/invoices-filament or kitloom/invoices-nova; the shop is kitloom/shop.

The module keeps everything where WooCommerce's PDF Invoices & Packing Slips plugin (WP Overnight) keeps it: a shop moved from WordPress keeps its invoices — their numbers, dates and the settings they were made with — and the next invoice goes on from the last one.

Setting it up ​

Set up invoices
  1. Invoices → Settings → Shop details: the name, address, logo, VAT and registration numbers, the footer (terms, return policy) and up to three extra blocks for the bottom of the page. Left empty, the invoice prints the site title and the store address of Shop → Settings.
  2. Invoice → Attach to: the emails that carry the invoice — usually "Processing order" or "Completed order". None is ticked on a new shop: nothing is numbered until you say how.
  3. Invoice numbers: a prefix and a suffix (INV-[invoice_year]-), how many digits the number is padded to (4 makes 0042), whether the numbering starts again from 1 every year. "Next invoice number" is where the numbering goes on from — change it only to continue a numbering kept elsewhere.
  4. Customer's account: when the customer finds an "Invoice" link on their order — once the invoice exists (the default), always, for some statuses, or never.
  5. Open an order: Invoice and Packing slip are on its row and its page. Invoice number sets the number and the date by hand.

Placeholders in the prefix and the suffix: [invoice_year], [invoice_month], [invoice_day], [order_year], [order_month], [order_day], [order_number], and a date in a PHP format — [invoice_date="ymd"], [order_date="Y"]. Dates are in the site's timezone (Settings → General).

When an order gets its invoice ​

Once, and for good: the order keeps its number whatever happens to it. It gets one

  • when one of the emails ticked under "Attach to" is sent — never with the stock emails or "New account";
  • when the order reaches a status ticked under "Make the invoice when an order is";
  • when someone opens "Invoice" on the order in the admin, or an agent asks for it (invoices_create);
  • when the customer downloads it while the account offers it before it exists ("Always", or the statuses you chose);
  • when you set its number by hand.

It gets none while invoices are off, while the order's status is ticked under "No invoice for orders that are", or for an order of 0 when "No invoice for free orders" is on. An invoice made already is always there to download.

The numbers come from one counter for the whole shop, taken under a lock: two orders paid at the same moment never get the same number. With "Start again from 1 every year" the first invoice of January is 1 again; an invoice dated by its order (Display the date → Order date) is counted in its order's year.

An invoice is drawn with the shop details it was made with: the shop that moves keeps its old address on its old invoices. "Always draw invoices with the current settings" draws every invoice with today's.

What the documents show ​

The invoice: your details and logo, the billing address (and the shipping address — when it differs, always or never), the email and phone, the invoice number and date (or the order's), the order number and date, the payment method, the lines with their SKU, quantity and price, the totals as the order's email has them — discounts, shipping, fees, taxes, refunds — the customer's note, your footer and extra blocks.

The packing slip: the shipping address (and the billing one as you chose), the order number and date, the shipping method, the lines with their SKU, weight and quantity — no prices.

They speak the language the customer ordered in, as the shop's emails to them do. "How the PDF opens" decides whether it opens in a new tab or downloads; the paper is A4 or Letter.

Who opens a PDF ​

  • The shop's staff — whoever may edit the shop's orders (edit_shop_orders) — opens both documents of any order.
  • The customer opens their own order's invoice while their account offers it, signed in. They never get the packing slip, nor anyone else's invoice: such an address answers "not found", as an order that does not exist does.
  • A link an agent hands out opens without signing in for 30 minutes (kitloom-invoices.signed_minutes).

The PDFs are not stored: each is drawn when it is opened or sent, from the order and its invoice number. Drawing them, dompdf keeps the fonts' metrics in storage/fonts; on a server whose storage is read-only, kitloom-invoices.pdf.font_cache names another folder the site may write to.

For AI agents ​

With kitloom/ai: invoices_get (an order's invoice number and date, and links to its PDFs) and invoices_create (number the order now). Both take the shop's order permission; the settings are the options below, which the settings tools change with the module's permission (manage_invoices).

Webhooks ​

With kitloom/webhooks: the topic invoice.created — the order (id, number, total, currency) and the invoice (number as printed, sequence, date) — for a bookkeeping app that files every invoice.

What is inside ​

WhatWhere
The invoice of an orderthe order's meta (wc_orders_meta): _wcpdf_invoice_number (as printed), _wcpdf_invoice_number_data (number, prefix, suffix, padding), _wcpdf_invoice_date (a timestamp) and _wcpdf_invoice_date_formatted, _wcpdf_invoice_settings (the settings it was made with), _wcpdf_invoice_creation_trigger; the packing slip's _wcpdf_packing-slip_date
The numberingthe plugin's table wcpdf_invoice_number (the site's prefix in front); with the yearly reset, last years' numbers in wcpdf_invoice_number_{year}
Settingsthe plugin's options wpo_wcpdf_settings_general, wpo_wcpdf_documents_settings_invoice, wpo_wcpdf_documents_settings_packing-slip; "Calculate document numbers" of its wpo_wcpdf_settings_debug is read
The PDFsthe views invoices::pdf.invoice and invoices::pdf.packing-slip, drawn by dompdf
The download address/invoices/{order}/invoice and /invoices/{order}/packing-slip (kitloom-invoices.routes)
Configurationconfig/kitloom-invoices.php

The plugin's options are read as WordPress left them (serialized, a ticked box 1, an unticked one missing, the texts by language with a default) and written as JSON from then on. The plugin's own PHP templates are not used: the invoice is drawn by the module's views.

The shop meets the module through its Kitloom\Shop\Contracts\OrderDocument (the attachments of its emails, the links on the customer's order) and the order screens' record actions (IndexRecordActions / IndexActions with the type shop_order); the shop names no module.

php
use Kitloom\Invoices\Documents\OrderInvoices;
use Kitloom\Invoices\Invoices;
use Kitloom\Invoices\Pdf\DocumentPdf;

$invoice = app(OrderInvoices::class)->create($order, Invoices::TRIGGER_SINGLE, $user); // null — this order gets none
$pdf = app(DocumentPdf::class)->invoice($order, $invoice);                              // the PDF's bytes

What the server needs ​

dompdf draws the PDF in PHP: nothing to install beside the dom, mbstring and gd extensions (gd for the logo), and a writable storage/fonts for its font metrics. It fetches nothing from the web — the logo goes in as data from the uploads disk. DejaVu Sans writes Latin, Cyrillic and Greek; dompdf does not join Arabic or Hebrew letters, and draws CSS 2.1 (tables, no flexbox or grid). dompdf is LGPL-2.1, used as it is published.

What the application overrides ​

  • The look: php artisan vendor:publish --tag=kitloom-invoices-views, then edit resources/views/vendor/invoices/pdf/*.blade.php.
  • The engine: bind Kitloom\Invoices\Contracts\PdfRenderer to one of your own (Gotenberg, Browsershot) in a provider of the application.
  • The download address: kitloom-invoices.routes.prefix, or register off (no download in the admins and the account; the emails still carry the invoice).
  • The agents' tools and the webhook topic: kitloom-invoices.ai.register, kitloom-invoices.webhooks.register.
  • The admin sections: kitloom-invoices.filament.register and kitloom-invoices.nova.register (php artisan vendor:publish --tag=kitloom-invoices-config).

Tests ​

From the application that installs the package: php vendor/bin/phpunit --testsuite Invoices.

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]