This package provides a robust, easy-to-use system for managing invoices within a Laravel application, with options for database storage, serial numbering, and PDF generation.
Try out the interactive demo to explore package capabilities.
- PHP 8.1+
- Laravel 11.0+
barryvdh/laravel-dompdf
for PDF renderingelegantly/laravel-money
for money computation
You can install the package via composer:
composer require finller/laravel-invoices
You can publish and run the migrations with:
php artisan vendor:publish --tag="invoices-migrations"
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="invoices-config"
This is the contents of the published config file:
use Finller\Invoice\Invoice;
use Finller\Invoice\InvoiceDiscount;
use Finller\Invoice\InvoiceItem;
use Finller\Invoice\InvoiceType;
return [
'model_invoice' => Invoice::class,
'model_invoice_item' => InvoiceItem::class,
'discount_class' => InvoiceDiscount::class,
'cascade_invoice_delete_to_invoice_items' => true,
'serial_number' => [
/**
* If true, will generate a serial number on creation
* If false, you will have to set the serial_number yourself
*/
'auto_generate' => true,
/**
* Define the serial number format used for each invoice type
*
* P: Prefix
* S: Serie
* M: Month
* Y: Year
* C: Count
* Example: IN0012-220234
* Repeat letter to set the length of each information
* Examples of formats:
* - PPYYCCCC : IN220123 (default)
* - PPPYYCCCC : INV220123
* - PPSSSS-YYCCCC : INV0001-220123
* - SSSS-CCCC: 0001-0123
* - YYCCCC: 220123
*/
'format' => [
InvoiceType::Invoice->value => 'PPYYCCCC',
InvoiceType::Quote->value => 'PPYYCCCC',
InvoiceType::Credit->value => 'PPYYCCCC',
InvoiceType::Proforma->value => 'PPYYCCCC',
],
/**
* Define the default prefix used for each invoice type
*/
'prefix' => [
InvoiceType::Invoice->value => 'IN',
InvoiceType::Quote->value => 'QO',
InvoiceType::Credit->value => 'CR',
InvoiceType::Proforma->value => 'PF',
],
],
'date_format' => 'Y-m-d',
'default_seller' => [
'name' => null,
'address' => [
'street' => null,
'city' => null,
'postal_code' => null,
'state' => null,
'country' => null,
],
'email' => null,
'phone_number' => null,
'tax_number' => null,
'company_number' => null,
],
/**
* ISO 4217 currency code
*/
'default_currency' => 'USD',
'pdf' => [
/**
* Default DOM PDF options
*
* @see Available options https://github.com/barryvdh/laravel-dompdf#configuration
*/
'options' => [
'isPhpEnabled' => true,
'fontHeightRatio' => 0.9,
/**
* Supported values are: 'DejaVu Sans', 'Helvetica', 'Courier', 'Times', 'Symbol', 'ZapfDingbats'
*/
'defaultFont' => 'Helvetica',
],
'paper' => [
'paper' => 'a4',
'orientation' => 'portrait',
],
/**
* The logo displayed in the PDF
*/
'logo' => null,
/**
* The color displayed at the top of the PDF
*/
'color' => '#050038',
/**
* The template used to render the PDF
*/
'template' => 'default.layout',
],
];
You can store an Invoice in your database using the Eloquent Model: Finller\Invoice\Invoice
.
Note
Don't forget to publish and run the migrations
Here is a full example:
use Finller\Invoice\Invoice;
use Finller\Invoice\InvoiceState;
use Finller\Invoice\InvoiceType;
// Let's say your app edit invoices for your users
$customer = User::find(1);
// Let's imagine that your users have purchased something in your app
$order = Order::find(2);
$invoice = new Invoice([
'type' => InvoiceType::Invoice,
'state' => InvoiceState::Draft,
'description' => 'A description for my invoice',
'seller_information' => config('invoices.default_seller'),
'buyer_information' => [
'name' => 'John Doe',
'address' => [
'street' => '8405 Old James St.Rochester',
'city' => 'New York',
'postal_code' => '14609',
'state' => 'New York (NY)',
'country' => 'United States',
],
'email' => 'john.doe@example.com',
'tax_number' => "FR123456789",
],
// ...
]);
$invoice->buyer()->associate($customer); // optionnally associate the invoice to any model
$invoice->invoiceable()->associate($order); // optionnally associate the invoice to any model
$invoice->save();
$invoice->items()->saveMany([
new InvoiceItem([
'unit_price' => Money::of(100, 'USD'),
'unit_tax' => Money::of(20, 'USD'),
'currency' => 'USD',
'quantity' => 1,
'label' => 'A label for my item',
'description' => 'A description for my item',
]),
]);
This package provides a simple and reliable way to generate serial numbers automatically, such as "INV240001".
You can configure the format of your serial numbers in the configuration file. The default format is PPYYCCCC
, where each letter has a specific meaning (see the config file for details).
When invoices.serial_number.auto_generate
is set to true
, a unique serial number is assigned to each new invoice automatically.
Serial numbers are generated sequentially, with each new serial number based on the latest available one. To define what qualifies as the previous
serial number, you can extend the Finller\Invoice\Invoice
class and override the getPreviousInvoice
method.
By default, the previous invoice is determined based on criteria such as prefix, series, year, and month for accurate, scoped numbering.
In more complex applications, you may need to use different prefixes and/or series for your invoices.
For instance, you might want to define a unique series for each user, creating serial numbers that look like: INV0001-2400X
, where 0001
represents the userโs ID, 24
the year and X
the index of the invoice.
Note
When using IDs for series, it's recommended to plan for future growth to avoid overflow. Even if you have a limited number of users now, ensure that the ID can accommodate the maximum number of digits allowed by the serial number format.
When creating an invoice, you can dynamically specify the prefix and series as follows:
use Finller\Invoice\Invoice;
$invoice = new Invoice();
$invoice->configureSerialNumber(
prefix: "ORG",
serie: $buyer_id,
);
In most cases, the format of your serial numbers should remain consistent, so it's recommended to set it in the configuration file.
The format you choose will determine the types of information you need to provide to configureSerialNumber
.
Below is an example of the most complex serial number format you can create with this package:
$invoice = new Invoice();
$invoice->configureSerialNumber(
format: "PP-SSSSSS-YYMMCCCC",
prefix: "IN",
serie: 100,
year: now()->format('Y'),
month: now()->format('m')
);
$invoice->save();
$invoice->serial_number; // IN-000100-24010001
The Invoice model has a toPdfInvoice()
that return a PdfInvoice
class.
You can stream the PdfInvoice
instance as a response, or download it:
namespace App\Http\Controllers;
use App\Models\Invoice;
use Illuminate\Http\Request;
class InvoiceController extends Controller
{
public function show(Request $request, string $serial)
{
/** @var Invoice $invoice */
$invoice = Invoice::where('serial_number', $serial)->firstOrFail();
$this->authorize('view', $invoice);
return $invoice->toPdfInvoice()->stream();
}
public function download(Request $request, string $serial)
{
/** @var Invoice $invoice */
$invoice = Invoice::where('serial_number', $serial)->firstOrFail();
$this->authorize('view', $invoice);
return $invoice->toPdfInvoice()->download();
}
}
The Invoice
model provide a toMailAttachment
method making it easy to use with Mailable
namespace App\Mail;
use App\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PaymentInvoice extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
protected Invoice $invoice,
) {}
public function attachments(): array
{
return [
$this->invoice->toMailAttachment()
];
}
}
To customize how your model is converted to a PdfInvoice
, follow these steps:
-
Create a Custom Model: Define your own
\App\Models\Invoice
class and ensure it extends the base\Finller\Invoice\Invoice
class. -
Override the
toPdfInvoice
Method: Implement your specific logic within thetoPdfInvoice
method to control the customization. -
Update the Configuration File: Publish the package configuration file and update the
model_invoice
key as shown below:return [ 'model_invoice' => \App\Models\Invoice::class, ];
If you need to set the logo dynamically on the invoice, for example, when allowing users to upload their own logo, you can achieve this by overriding the getLogo
method. Follow the steps outlined in the Customizing the PDF Invoice section to create your own Model.
To dynamically set the logo, define the getLogo
method as shown below:
class Invoice extends \Finller\Invoice\Invoice
{
// ...
public function getLogo(): ?string
{
$file = new File(public_path('logo.png'));
$mime = $file->getMimeType();
$logo = "data:{$mime};base64," . base64_encode($file->getContent());
return $logo;
}
}
Note
The returned value must be either a base64-encoded data URL or a path to a locally accessible file.
This package includes a standalone PdfInvoice
class, making it easy to render invoices as a PDF or directly within a view.
You can even use this package exclusively for the PdfInvoice
class if that suits your needs.
Hereโs an example of a fully configured PdfInvoice
instance:
use Finller\Invoice\PdfInvoice;
$pdfInvoice = new PdfInvoice(
name: "Invoice",
state: "Paid",
serial_number: "INV-241200001",
seller: [
'name' => 'elegantly',
'address' => [
'street' => "Place de l'Opรฉra",
'city' => 'Paris',
'postal_code' => '75009',
'country' => 'France',
],
'email' => 'john.doe@example.com',
'tax_number' => 'FR123456789',
"data" => [
"foo" => "bar"
]
],
buyer: [
'name' => 'John Doe',
'address' => [
'street' => '8405 Old James St.Rochester',
'city' => 'New York',
'postal_code' => '14609',
'state' => 'New York (NY)',
'country' => 'United States',
],
'email' => 'john.doe@example.com',
"data" => [
"foo" => "bar"
]
],
description: "A invoice description",
created_at: now(),
due_at: now(),
paid_at: now(),
tax_label: "VAT (20%)",
items: [],
discounts: [],
logo: public_path('/images/logo.png'),
template: "default.layout",
font: "Helvetica",
)
namespace App\Http\Controllers;
use Finller\Invoice\PdfInvoice;
class InvoiceController extends Controller
{
public function showAsView()
{
$pdfInvoice = new PdfInvoice(
// ...
);
return $pdfInvoice->view();
}
public function showAsPdf()
{
$pdfInvoice = new PdfInvoice(
// ...
);
return $pdfInvoice->stream();
}
public function download()
{
$pdfInvoice = new PdfInvoice(
// ...
);
return $pdfInvoice->download();
}
}
namespace App\Http\Controllers;
use Finller\Invoice\PdfInvoice;
class Invoice extends Component
{
public function download()
{
$pdfInvoice = new PdfInvoice(
// ...
);
return response()->streamDownload(function () use ($pdfInvoice) {
echo $pdf->output();
}, 'laravel-invoices-demo.pdf');
}
}
$pdfInvoice = new PdfInvoice(
// ...
);
Storage::put(
"path/to/invoice.pdf",
$pdfInvoice->pdf()->output()
);
You can render your invoice within a larger view, enabling you to create an "invoice builder" experience similar to the interactive demo.
To achieve this, include the main part of the invoice in your view as shown below:
<div class="aspect-[210/297] bg-white shadow-md">
@include('invoices::default.invoice', ['invoice' => $invoice])
</div>
This approach allows you to seamlessly integrate the invoice into a dynamic and customizable user interface.
Note
The default template is styled using Tailwind-compatible syntax, making it seamlessly compatible with websites that use Tailwind.
If you donโt use Tailwind, the styling may not render as intended.
See the Dompdf font guide.
To customize the invoice template, you can publish the provided views and modify them as needed.
Alternatively, you can create a completely custom template. Ensure that your custom template follows the same structure and conventions as the default one to maintain compatibility with various use cases.
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.