Invoicing Guide
Last Updated: 2026-01-13 Status: Implemented Plan Reference: 008-invoicing-system.md, 072-invoicing-improvement.md
Overview
The Invoicing system allows administrators to create, manage, and track invoices for clients. Invoices support multiple line items, status tracking, PDF generation, online payments via Stripe, recurring invoices, and automated payment reminders. Clients can view and pay their invoices through the portal.
Table of Contents
- Accessing Invoices
- Features
- Invoice Status Workflow
- How to Use
- Line Items
- Online Payments (Stripe)
- Recurring Invoices
- Payment Reminders
- Technical Architecture
- Related Features
Accessing Invoices
Navigation
| Access Point | Location | URL | Role |
|---|---|---|---|
| All Invoices | Admin sidebar | /admin/invoices | Admin |
| Create Invoice | Invoices page | /admin/invoices/create | Admin |
| View Invoice | Invoice row | /admin/invoices/{id} | Admin |
| Edit Invoice | Invoice detail | /admin/invoices/{id}/edit | Admin |
| My Invoices | Portal sidebar | /portal/invoices | Client User |
| Invoice Detail | Portal | /portal/invoices/{id} | Client User |
| Invoice PDF | Invoice page | /admin/invoices/{id}/pdf | Admin |
| Download PDF | Invoice page | /admin/invoices/{id}/pdf/download | Both |
Permissions
| Action | Admin | Client User |
|---|---|---|
| View all invoices | ✅ | ❌ |
| View client's invoices | ✅ | ✅ |
| Create invoice | ✅ | ❌ |
| Edit invoice | ✅ | ❌ |
| Delete invoice | ✅ | ❌ |
| Mark as paid | ✅ | ❌ |
| Download PDF | ✅ | ✅ |
Features
Invoice Details
- Invoice number (auto-generated)
- Client association
- Issue date and due date
- Status tracking
- Notes for client
- Internal notes
Line Items
- Multiple line items per invoice
- Description, quantity, rate
- Automatic line total calculation
- Automatic invoice total
Invoice Totals
- Subtotal (sum of line items)
- Tax calculation (optional)
- Discount (optional)
- Grand total
PDF Generation
- Professional PDF invoices
- Company branding
- Downloadable format
- See PDF Generation Guide
Invoice Status Workflow
Status Enum
Location: app/Enums/InvoiceStatus.php
| Status | Value | Description | Color |
|---|---|---|---|
| Draft | draft | Not sent to client | Gray |
| Sent | sent | Sent, awaiting payment | Blue |
| Paid | paid | Payment received | Green |
| Overdue | overdue | Past due date | Red |
Status Transitions
Draft → Sent → Paid
↓
Overdue (automatic after due date)
Status Methods
// Check status
$invoice->status->isDraft();
$invoice->status->isSent();
$invoice->status->isPaid();
$invoice->status->isOverdue();
// Get display values
$invoice->status->label(); // "Sent"
$invoice->status->color(); // "blue"
How to Use
Creating an Invoice
- Navigate to Admin → Invoices
- Click "Create Invoice"
- Select Client from dropdown
- Set Issue Date (defaults to today)
- Set Due Date
- Add Line Items:
- Click "Add Line Item"
- Enter description
- Enter quantity
- Enter unit rate
- Line total calculates automatically
- Add optional notes
- Click "Create Invoice"
Adding Line Items
Each line item requires:
- Description: What the charge is for
- Quantity: Number of units (default: 1)
- Rate: Price per unit
Line Total = Quantity × Rate
Invoice Total = Sum of all Line Totals
Viewing an Invoice
- Navigate to Admin → Invoices or Portal → Invoices
- Click invoice number or "View"
- See full invoice details:
- Client information
- Line items breakdown
- Status and dates
- Total amount
Editing an Invoice
- Navigate to Admin → Invoices → [Invoice]
- Click "Edit"
- Modify fields or line items
- Click "Update Invoice"
Note: Only draft invoices should be edited. Sent invoices should be voided and recreated.
Changing Invoice Status
Mark as Sent:
- View invoice
- Click "Mark as Sent"
- Status changes to "Sent"
Mark as Paid:
- View invoice
- Click "Mark as Paid"
- Status changes to "Paid"
- Paid date recorded
Generating PDF
- View any invoice
- Click "View PDF" to open in browser
- Click "Download PDF" to save file
See PDF Generation Guide for details.
Deleting an Invoice
- Navigate to Admin → Invoices → [Invoice]
- Click "Delete"
- Confirm deletion
Warning: Only delete draft invoices. Consider voiding sent invoices instead.
Line Items
Structure
Each invoice has multiple line items:
// InvoiceItem model
$item->description; // "Web Development Services"
$item->quantity; // 10
$item->unit_price; // 150.00
$item->total; // 1500.00 (calculated)
Adding Items Dynamically
The invoice form uses JavaScript to add/remove line items:
// Add new line item row
addLineItem();
// Remove line item row
removeLineItem(index);
// Recalculate totals
updateTotals();
Calculations
// Line item total
$lineTotal = $quantity * $unitPrice;
// Invoice subtotal
$subtotal = $invoice->items->sum('total');
// With tax (if applicable)
$tax = $subtotal * ($taxRate / 100);
$total = $subtotal + $tax;
Online Payments (Stripe)
Overview
Clients can pay invoices online via Stripe Checkout. When Stripe is configured, a "Pay Now" button appears on unpaid invoices in the client portal.
Configuration
Add these environment variables to .env:
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Getting Stripe Credentials
- Create a Stripe account if you don't have one
- Go to Stripe Dashboard → Developers → API keys
- Copy the Publishable key →
STRIPE_KEY - Copy the Secret key →
STRIPE_SECRET
Setting Up the Webhook
- Go to Stripe Dashboard → Developers → Webhooks
- Click "Add endpoint"
- Enter your endpoint URL:
https://your-domain.com/webhooks/stripe - Select events to listen to:
checkout.session.completedpayment_intent.succeededpayment_intent.payment_failed
- Click "Add endpoint"
- Copy the Signing secret →
STRIPE_WEBHOOK_SECRET
Note: For local development, use Stripe CLI to forward webhooks:
stripe listen --forward-to localhost:8000/webhooks/stripe
Features
- Stripe Checkout: Secure, hosted payment page
- Multiple Payment Methods: Credit cards supported
- Automatic Status Update: Invoice marked as paid after successful payment
- Payment Recording: All payments tracked in the database
- Webhook Support: Real-time payment confirmation
Payment Flow
- Client clicks "Pay Now" on invoice
- Redirected to Stripe Checkout
- Completes payment
- Webhook confirms payment
- Invoice status updated to "Paid"
- Client sees success page
Payment Model
Location: app/Models/Payment.php
$payment->invoice_id; // Related invoice
$payment->amount; // Payment amount
$payment->payment_method; // 'stripe', 'manual', etc.
$payment->transaction_id; // Stripe payment intent ID
$payment->status; // 'completed', 'pending', 'failed'
$payment->paid_at; // Payment timestamp
Routes
| Route | Method | Description |
|---|---|---|
portal.invoices.payment.checkout | POST | Initiate Stripe Checkout |
portal.invoices.payment.success | GET | Payment success page |
webhooks.stripe | POST | Stripe webhook handler |
Recurring Invoices
Overview
Set up recurring invoices that automatically generate on a schedule. Useful for retainers, subscriptions, and maintenance contracts.
Access
| Location | URL | Role |
|---|---|---|
| Recurring Invoices List | /admin/recurring-invoices | Admin |
| Create Recurring Invoice | /admin/recurring-invoices/create | Admin |
| View Details | /admin/recurring-invoices/{id} | Admin |
| Edit | /admin/recurring-invoices/{id}/edit | Admin |
Frequencies
| Frequency | Label | Days Between |
|---|---|---|
weekly | Weekly | 7 |
biweekly | Bi-weekly | 14 |
monthly | Monthly | 30 |
quarterly | Quarterly | 90 |
annually | Annually | 365 |
Creating a Recurring Invoice
- Navigate to Admin → Recurring Invoices
- Click "New Recurring Invoice"
- Fill in details:
- Name: Descriptive name (e.g., "Monthly Retainer")
- Client: Select client
- Frequency: Select billing frequency
- Start Date: When to begin
- End Date: Optional end date
- Line Items: Template items for generated invoices
- Click "Create Recurring Invoice"
Actions
| Action | Description |
|---|---|
| Generate Now | Manually generate an invoice immediately |
| Toggle Status | Activate/deactivate the recurring invoice |
| Edit | Modify template details |
| Delete | Remove the recurring invoice |
Scheduled Processing
Recurring invoices are processed daily at 7:00 AM. The ProcessRecurringInvoicesJob checks for due recurring invoices and generates them automatically.
Configuration
// config/invoicing.php
'recurring' => [
'auto_send' => true, // Auto-send when generated
'default_payment_terms' => 30, // Days until due
],
Payment Reminders
Overview
Automated email reminders are sent for upcoming and overdue invoices. Reminders help improve collection rates without manual follow-up.
Reminder Schedule
Before Due Date (configurable):
- 7 days before due
- 3 days before due
- 1 day before due
After Due Date (overdue):
- 1 day overdue
- 7 days overdue
- 14 days overdue
- 30 days overdue
Configuration
// config/invoicing.php
'reminders' => [
'enabled' => true,
'days_before_due' => [7, 3, 1],
'days_after_due' => [1, 7, 14, 30],
],
Enable/disable via environment:
INVOICE_REMINDERS_ENABLED=true
Scheduled Job
Reminders are sent daily at 9:00 AM via the SendInvoiceRemindersJob.
Notification Content
Upcoming Reminders:
- Subject: "Payment Reminder: Invoice INV-2026-0001"
- Tone: Friendly reminder
Overdue Reminders:
- Subject: "Overdue Invoice: INV-2026-0001"
- Tone: More urgent, includes days overdue
Technical Architecture
Models
Invoice Model: app/Models/Invoice.php
class Invoice extends Model
{
protected $fillable = [
'client_id',
'project_id',
'invoice_number',
'status',
'issue_date',
'due_date',
'paid_date',
'subtotal',
'tax',
'total',
'notes',
'internal_notes',
];
protected $casts = [
'status' => InvoiceStatus::class,
'issue_date' => 'date',
'due_date' => 'date',
'paid_date' => 'date',
'subtotal' => 'decimal:2',
'tax' => 'decimal:2',
'total' => 'decimal:2',
];
}
InvoiceItem Model: app/Models/InvoiceItem.php
class InvoiceItem extends Model
{
protected $fillable = [
'invoice_id',
'description',
'quantity',
'unit_price',
'total',
];
protected $casts = [
'quantity' => 'decimal:2',
'unit_price' => 'decimal:2',
'total' => 'decimal:2',
];
}
Relationships
// Invoice model
public function client(): BelongsTo
{
return $this->belongsTo(Client::class);
}
public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}
public function items(): HasMany
{
return $this->hasMany(InvoiceItem::class);
}
// InvoiceItem model
public function invoice(): BelongsTo
{
return $this->belongsTo(Invoice::class);
}
Controller
Location: app/Http/Controllers/Admin/InvoiceController.php
| Method | Route | Description |
|---|---|---|
index() | GET /admin/invoices | List invoices |
create() | GET /admin/invoices/create | Create form |
store() | POST /admin/invoices | Create invoice |
show() | GET /admin/invoices/{invoice} | View invoice |
edit() | GET /admin/invoices/{invoice}/edit | Edit form |
update() | PUT /admin/invoices/{invoice} | Update invoice |
destroy() | DELETE /admin/invoices/{invoice} | Delete invoice |
pdf() | GET /admin/invoices/{invoice}/pdf | View PDF |
downloadPdf() | GET /admin/invoices/{invoice}/pdf/download | Download PDF |
Routes
// Admin routes
Route::prefix('admin')->middleware(['auth', 'admin'])->group(function () {
Route::resource('invoices', Admin\InvoiceController::class);
Route::get('invoices/{invoice}/pdf', [Admin\InvoiceController::class, 'pdf'])->name('admin.invoices.pdf');
Route::get('invoices/{invoice}/pdf/download', [Admin\InvoiceController::class, 'downloadPdf'])->name('admin.invoices.pdf.download');
});
// Portal routes
Route::prefix('portal')->middleware(['auth'])->group(function () {
Route::get('invoices', [Portal\InvoiceController::class, 'index'])->name('portal.invoices.index');
Route::get('invoices/{invoice}', [Portal\InvoiceController::class, 'show'])->name('portal.invoices.show');
Route::get('invoices/{invoice}/pdf', [Portal\InvoiceController::class, 'pdf'])->name('portal.invoices.pdf');
Route::get('invoices/{invoice}/pdf/download', [Portal\InvoiceController::class, 'downloadPdf'])->name('portal.invoices.pdf.download');
});
Database Schema
Table: invoices
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
client_id | bigint | Foreign key to clients |
project_id | bigint | Optional foreign key to projects |
invoice_number | string | Unique invoice number |
status | string | Status enum value |
issue_date | date | Date issued |
due_date | date | Payment due date |
paid_date | date | Date payment received |
subtotal | decimal | Sum of line items |
tax | decimal | Tax amount |
total | decimal | Final total |
notes | text | Client-visible notes |
internal_notes | text | Admin-only notes |
created_at | timestamp | Creation date |
updated_at | timestamp | Last update |
Table: invoice_items
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
invoice_id | bigint | Foreign key to invoices |
description | string | Line item description |
quantity | decimal | Quantity |
unit_price | decimal | Price per unit |
total | decimal | Line total |
created_at | timestamp | Creation date |
updated_at | timestamp | Last update |
Views
| View | Path | Purpose |
|---|---|---|
| Index | resources/views/admin/invoices/index.blade.php | Invoice list |
| Create | resources/views/admin/invoices/create.blade.php | Create form |
| Show | resources/views/admin/invoices/show.blade.php | Invoice details |
| Edit | resources/views/admin/invoices/edit.blade.php | Edit form |
| Portal Index | resources/views/portal/invoices/index.blade.php | Client's invoices |
| Portal Show | resources/views/portal/invoices/show.blade.php | Client's view |
| PDF Template | resources/views/pdf/invoice.blade.php | PDF template |
Scopes and Query Helpers
// By status
Invoice::status(InvoiceStatus::Sent)->get();
// Overdue invoices
Invoice::overdue()->get();
// For a client
Invoice::forClient($client)->get();
// Unpaid invoices
Invoice::unpaid()->get();
// Date range
Invoice::issuedBetween($start, $end)->get();
// With totals
Invoice::withItemsTotal()->get();
Related Features
Dependencies
| Feature | Relationship |
|---|---|
| Client Management | Invoices belong to clients |
| Project Management | Optional project association |
| Authorization | InvoicePolicy controls access |
Complementary Features
| Feature | Description |
|---|---|
| PDF Generation | Invoice PDF output |
| Notifications | Invoice email notifications |
| Reports | Financial reporting |
| Activity Logging | Invoice change history |
Best Practices
For Administrators
- Use descriptive line items for clarity
- Set realistic due dates (typically Net 30)
- Send invoices promptly after work is completed
- Mark paid immediately when payment received
- Use notes for payment instructions
- Use internal notes for accounting references
For Developers
- Use InvoiceStatus enum for status values
- Calculate totals server-side for accuracy
- Validate line items ensure at least one item
- Use transactions when creating invoice with items
Troubleshooting
| Issue | Solution |
|---|---|
| Total not calculating | Check line item quantities and rates |
| PDF not generating | Verify dompdf is installed |
| Client can't see invoice | Check client association |
| Invoice number duplicate | Auto-generation handles this |
See Also
- PDF Generation - Invoice PDF output
- Client Management - Client organization
- Reports - Financial reports
- Notifications - Email notifications