Looking to hire Laravel developers? Try LaraJobs

laravel-billing maintained by gusmanwidodo

Description
Generic, multi-purpose billing & invoicing for Laravel. Polymorphic billable, integer-minor-unit money (no float errors), line items with tax & discount, computed totals, and payment recording. Payment-gateway agnostic.
Author
Gusman Widodo
Last update
2026/08/26 15:27 (dev-main)
License
Downloads
13

Comments
comments powered by Disqus

Laravel Billing

Generic, multi-purpose billing & invoicing for Laravel. Attach invoices to any model (user, organization, tenant, project — anything), store money safely as integer minor units (no floating-point errors), build invoices with line items, tax, and discounts, and record payments. Payment-gateway agnostic by design.

Tests License: MIT

Why

  • Multi-purpose: invoices are polymorphic — bill a User, an Organization, a Tenant, a Project, or nothing at all.
  • Money done right: all amounts are integer minor units (cents) plus a currency code, so 0.1 + 0.2 is exactly 0.30 — never a float rounding bug.
  • Computed totals: subtotal, tax, and total are derived from line items and persisted; recompute any time.
  • Gateway-agnostic: the package records payments; it does not talk to Stripe, Midtrans, etc. Bring your own gateway and call recordPayment().

Requirements

  • PHP ^8.3
  • Laravel 12

Installation

composer require gusmanwidodo/laravel-billing
php artisan migrate
# optionally:
php artisan vendor:publish --tag=billing-config
php artisan vendor:publish --tag=billing-migrations

Quick start

use Gusmanwidodo\Billing\Facades\Billing;

$invoice = Billing::newInvoice('USD')
    ->addItem('Pro plan', unitPrice: 5000, quantity: 2, taxRate: 0.10) // $50.00 x2, 10% tax
    ->addItem('Setup fee', unitPrice: 1500, discount: 500)             // $15.00 - $5.00
    ->discount(1000)      // invoice-level discount: $10.00
    ->dueInDays(14)
    ->create();

$invoice->total;                 // integer minor units, e.g. 11000
$invoice->total()->format();     // "110.00 USD"

All prices are integer minor units in the invoice currency (e.g. 5000 = $50.00). Use the Money helper to convert:

use Gusmanwidodo\Billing\Support\Money;

Money::fromMajor(50.00, 'USD')->amount;   // 5000
Money::of(5000, 'USD')->format();          // "50.00 USD"

Billing any model

Add the HasInvoices trait to whatever you bill:

use Gusmanwidodo\Billing\Concerns\HasInvoices;

class Organization extends Model
{
    use HasInvoices;
}

$org->invoice('USD')
    ->addItem('Enterprise seat', 100000, quantity: 5)
    ->create();

$org->invoices;         // all invoices billed to this org

The invoice's billable is a polymorphic relation, so the same package bills users, orgs, tenants, or anything else — no per-type tables.

Totals

For each line item:

line subtotal = quantity * unit_price - discount
line tax      = round(line subtotal * tax_rate)

For the invoice:

subtotal = Σ line subtotals
tax_total = Σ line taxes
total    = subtotal - invoice_discount + tax_total

All integer arithmetic. Call Billing::recompute($invoice) after changing items.

Payments

// Record a payment (your gateway/cash/transfer — the package is agnostic):
Billing::recordPayment($invoice, 4000, attributes: [
    'method' => 'transfer',
    'reference' => 'TRX-123',
]);

$invoice->refresh();
$invoice->status;                 // 'partially_paid'
$invoice->amountDue()->format();  // remaining balance

Billing::recordPayment($invoice, 7000);
$invoice->refresh();
$invoice->status;                 // 'paid' (amount_paid >= total)
$invoice->paid_at;                // set automatically

amount_paid is always the sum of recorded payments; status is derived (partially_paid / paid). Payments must match the invoice currency and be positive.

Void

Billing::void($invoice);   // status = 'void'; further payments are rejected

Status

Status is free-form — your app owns the workflow. Convenience constants are provided, and the package sets partially_paid / paid automatically as payments come in (and void when you void):

use Gusmanwidodo\Billing\Support\InvoiceStatus;

InvoiceStatus::DRAFT;           // 'draft'
InvoiceStatus::SENT;            // 'sent'
InvoiceStatus::PARTIALLY_PAID;  // 'partially_paid'
InvoiceStatus::PAID;            // 'paid'
InvoiceStatus::VOID;            // 'void'

Money & currencies

  • Amounts are stored as bigInteger minor units; never floats.
  • Money::fromMajor(10.50, 'USD')1050. Pass decimals: for zero-decimal currencies (e.g. JPY): Money::fromMajor(1000, 'JPY', decimals: 0).
  • Money supports add, subtract, multiply, percentage, format, and refuses cross-currency arithmetic.

Config

config/billing.php:

'default_currency' => 'USD',
'number_prefix'    => 'INV-',   // auto invoice numbers: INV-20260101-AB12CD
'decimals'         => 2,

Testing

composer test   # 17 tests, incl. money math, polymorphic billable, payments

Roadmap

  • Payment-gateway adapters (Stripe / Midtrans) as separate packages.
  • Recurring/subscription invoices.
  • PDF rendering.

License

MIT © Gusman Widodo. See LICENSE.