Overview
Commerce's recurring billing engine runs entirely on scheduled Artisan commands. There is no background daemon — the Laravel task scheduler drives everything. Add this single cron entry to your server to enable all scheduled commands:
* * * * * cd /opt/opterius && php artisan schedule:run >> /dev/null 2>&1
Scheduled Commands
| Command | Time | Purpose |
|---|---|---|
commerce:generate-renewal-invoices |
Daily 00:00 | Creates invoices for services approaching renewal |
commerce:mark-overdue-invoices |
Daily 00:30 | Marks unpaid past-due invoices as overdue |
commerce:check-overdue-services |
Daily 01:00 | Suspends services past the grace period |
commerce:check-expiring-domains |
Daily 02:00 | Generates domain renewal invoices 30 days before expiry |
How Renewal Invoice Generation Works
commerce:generate-renewal-invoices runs at 00:00 each day and:
- Finds all active services where
next_due_dateis within the renewal window (default: 7 days, configurable in Admin → Settings → Billing → Renewal Window) - Checks that no
unpaidoroverdueinvoice already exists for that service — prevents duplicate renewal invoices - Creates a new invoice with line items matching the service's product pricing
- Sets the due date based on your configured payment terms
- Sends the Invoice Created email to the client
[!TIP] A renewal window of 7 days means: if a service is due on April 20, Commerce creates the renewal invoice on April 13. This gives the client a week to pay before the service expires.
Billing Cycle Advancement
When a renewal invoice is paid:
- The invoice status moves to
paid - The service's
next_due_dateadvances by one billing cycle
Billing cycle periods:
| Cycle | next_due_date advance |
|---|---|
| Monthly | +1 month |
| Quarterly | +3 months |
| Semi-annual | +6 months |
| Annual | +1 year |
| Biennial | +2 years |
[!IMPORTANT]
next_due_dateis always calculated from the previousnext_due_date, not from the payment date. A late payment does not reset the billing anchor — the client is still billed on the original cycle.
Domain Renewals
commerce:check-expiring-domains (runs at 02:00) works separately from hosting renewal. It queries the domain registrar API for each domain registered through Commerce and generates renewal invoices 30 days before expiry. The 30-day window is fixed and not configurable in the current release.
Verifying the Scheduler
Check that all scheduled commands are registered and their next run time:
php artisan schedule:list
To test renewal invoice generation without waiting for midnight:
php artisan commerce:generate-renewal-invoices --dry-run
The --dry-run flag logs which services would receive invoices without creating anything.
To actually run the command immediately (useful after initial setup):
php artisan commerce:generate-renewal-invoices
[!WARNING] Running
commerce:generate-renewal-invoicesmanually without--dry-runwill create real invoices and send emails. Only do this if you intend to generate invoices immediately.
Idempotency
The commands are safe to run multiple times. The duplicate-check ensures that running commerce:generate-renewal-invoices twice in one day does not create two invoices for the same service. The notified_at guard in commerce:mark-overdue-invoices prevents duplicate overdue emails.
Monitoring
Log entries are written to storage/logs/laravel.log for every invoice created and every service suspended. Review this file if you suspect invoices are not being generated:
grep "renewal-invoices" storage/logs/laravel.log | tail -20