Paymenter relies on scheduled tasks (cron jobs) to automate critical billing operations, service lifecycle management, and system maintenance.
Required Cron Job
Paymenter requires a single cron job entry that runs Laravel’s task scheduler:
* * * * * cd /path-to-paymenter && php artisan schedule:run >> /dev/null 2>&1
This cron job must run every minute. All scheduled tasks are managed by Laravel’s scheduler.
Adding the Cron Job
Add the cron entry
* * * * * cd /var/www/paymenter && php artisan schedule:run >> /dev/null 2>&1
Replace /var/www/paymenter with your actual Paymenter installation path.
Verify it's running
Check the last scheduler run time in the admin panel under Settings > System, or run: php artisan schedule:list
Scheduled Tasks
Paymenter schedules the following tasks automatically:
Scheduler Heartbeat
Command: app:schedule-heartbeat
Frequency: Every minute
Purpose: Updates the last scheduler run timestamp
Schedule :: command ( ScheduleHeartbeatCommand :: class )
-> description ( 'Updates the last scheduler run time' )
-> everyMinute ()
-> onOneServer ();
This helps administrators verify the cron job is working correctly.
Main Cron Job
Command: app:cron-job
Frequency: Daily at configured time (default: 00:00)
Purpose: Handles all billing and service management automation
Schedule :: command ( CronJob :: class )
-> description ( 'Runs daily to send out invoices, suspend servers, etc.' )
-> dailyAt ( config ( 'settings.cronjob_time' , '00:00' ))
-> onOneServer ();
You can configure the cron job execution time in Settings > System > Cron Job Time.
Email Fetching
Command: app:fetch-emails
Frequency: Every 5 minutes
Purpose: Imports ticket replies via IMAP (if enabled)
Schedule :: command ( FetchEmails :: class )
-> description ( 'Import ticket emails using IMAP' )
-> everyFiveMinutes ()
-> withoutOverlapping ()
-> onOneServer ();
Telemetry
Command: app:telemetry
Frequency: Daily at random time (if enabled)
Purpose: Sends anonymous usage statistics
if ( config ( 'app.telemetry_enabled' )) {
Schedule :: command ( TelemetryCommand :: class )
-> description ( 'Sends telemetry data' )
-> dailyAt ( $settings [ 'hour' ] . ':' . $settings [ 'minute' ]);
}
Main Cron Job Tasks
The app:cron-job command performs multiple critical operations:
1. Invoice Generation
Task: invoices_created
Trigger: Service expiring within X days (default: 7)
Automatically creates renewal invoices for active services:
Invoice Creation
Free Services
Credit Payment
Billing Agreements
Service :: where ( 'status' , 'active' )
-> where ( 'expires_at' , '<' , now () -> addDays (( int ) config ( 'settings.cronjob_invoice' , 7 )))
-> get ()
-> each ( function ( $service ) {
// Skip if pending invoice exists
if ( $service -> invoices () -> where ( 'status' , 'pending' ) -> exists ()) {
return ;
}
// Create invoice
$invoice = $service -> invoices () -> create ([
'user_id' => $service -> user_id ,
'status' => 'pending' ,
'due_at' => $service -> expires_at ,
'currency_code' => $service -> currency_code ,
]);
// Add invoice items
$invoice -> items () -> create ([
'reference_id' => $service -> id ,
'reference_type' => Service :: class ,
'price' => $service -> price ,
'quantity' => $service -> quantity ,
'description' => $service -> description ,
]);
});
2. Order Cancellation
Task: orders_cancelled
Trigger: Pending services with unpaid first invoice after X days (default: 7)
Cancels orders where the initial payment was never completed:
Service :: where ( 'status' , 'pending' )
-> whereDoesntHave ( 'invoices' , function ( $query ) {
$query -> where ( 'status' , 'paid' );
})
-> where ( 'created_at' , '<' , now () -> subDays (
( int ) config ( 'settings.cronjob_order_cancel' , 7 )
))
-> each ( function ( $service ) {
$service -> invoices ()
-> where ( 'status' , 'pending' )
-> update ([ 'status' => 'cancelled' ]);
$service -> update ([ 'status' => 'cancelled' ]);
// Return stock
if ( $service -> product -> stock !== null ) {
$service -> product -> increment ( 'stock' , $service -> quantity );
}
});
3. Service Suspension
Task: services_suspended
Trigger: Active services overdue by X days (default: 2)
Suspends services with unpaid invoices:
Service :: where ( 'status' , 'active' )
-> where ( 'expires_at' , '<' , now () -> subDays (
( int ) config ( 'settings.cronjob_order_suspend' , 2 )
))
-> each ( function ( $service ) {
SuspendJob :: dispatch ( $service );
$service -> update ([ 'status' => 'suspended' ]);
});
4. Service Termination
Task: services_terminated
Trigger: Suspended services overdue by X days (default: 14)
Terminates long-overdue suspended services:
Service :: where ( 'status' , 'suspended' )
-> where ( 'expires_at' , '<' , now () -> subDays (
( int ) config ( 'settings.cronjob_order_terminate' , 14 )
))
-> each ( function ( $service ) {
TerminateJob :: dispatch ( $service );
$service -> update ([ 'status' => 'cancelled' ]);
// Cancel outstanding invoices
$service -> invoices ()
-> where ( 'status' , 'pending' )
-> update ([ 'status' => 'cancelled' ]);
// Return stock
if ( $service -> product -> stock !== null ) {
$service -> product -> increment ( 'stock' , $service -> quantity );
}
});
5. Upgrade Invoice Updates
Task: upgrade_invoices_updated
Trigger: Pending service upgrades
Recalculates upgrade invoice amounts and cancels expired upgrades:
ServiceUpgrade :: where ( 'status' , 'pending' )
-> each ( function ( $upgrade ) {
// Cancel if service already expired
if ( $upgrade -> service -> expires_at < now ()) {
$upgrade -> update ([ 'status' => 'cancelled' ]);
$upgrade -> invoice ?-> update ([ 'status' => 'cancelled' ]);
return ;
}
// Update invoice price
$upgrade -> invoice -> items () -> update ([
'price' => $upgrade -> calculatePrice () -> price ,
]);
});
6. Ticket Auto-Close
Task: tickets_closed
Trigger: Tickets with no response for X days (default: 7)
Automatically closes inactive tickets:
Ticket :: where ( 'status' , 'replied' )
-> each ( function ( $ticket ) {
$lastMessage = $ticket -> messages () -> latest ( 'created_at' ) -> first ();
if ( $lastMessage && $lastMessage -> created_at < now () -> subDays (
( int ) config ( 'settings.cronjob_close_ticket' , 7 )
)) {
$ticket -> update ([ 'status' => 'closed' ]);
}
});
7. Email Log Cleanup
Task: email_logs_deleted
Trigger: Email logs older than X days (default: 90)
Cleans up old notification logs:
Notification :: where ( 'created_at' , '<' , now () -> subDays (
( int ) config ( 'settings.cronjob_delete_email_logs' , 90 )
)) -> delete ();
Cron Job Configuration
Configure timing and thresholds in the admin panel:
Setting Default Description cronjob_time00:00Time to run daily cron job cronjob_invoice7Days before expiry to create invoice cronjob_order_cancel7Days to cancel unpaid orders cronjob_order_suspend2Days overdue before suspension cronjob_order_terminate14Days overdue before termination cronjob_close_ticket7Days inactive before auto-close cronjob_delete_email_logs90Days to keep email logs
Queue Workers
Paymenter uses database queues for background jobs. You must run a queue worker:
php artisan queue:work --tries=3 --timeout=90
The queue worker must be running continuously. Use a process monitor like Supervisor.
Supervisor Configuration
Create /etc/supervisor/conf.d/paymenter-worker.conf:
[program:paymenter-worker]
process_name =%(program_name)s_%(process_num)02d
command =php /var/www/paymenter/artisan queue:work -- sleep =3 -- tries =3 -- max-time =3600
autostart =true
autorestart =true
stopasgroup =true
killasgroup =true
user =www-data
numprocs =2
redirect_stderr =true
stdout_logfile =/var/www/paymenter/storage/logs/worker.log
stopwaitsecs =3600
Start the worker:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start paymenter-worker: *
Systemd Service
Alternatively, create a systemd service at /etc/systemd/system/paymenter-worker.service:
[Unit]
Description =Paymenter Queue Worker
After =network.target
[Service]
User =www-data
Group =www-data
Restart =always
ExecStart =/usr/bin/php /var/www/paymenter/artisan queue:work -- sleep =3 -- tries =3
[Install]
WantedBy =multi-user.target
Enable and start:
sudo systemctl enable paymenter-worker
sudo systemctl start paymenter-worker
Running Tasks Manually
You can run scheduled tasks manually for testing:
# Run all scheduled tasks that are due
php artisan schedule:run
# Run specific commands
php artisan app:cron-job
php artisan app:fetch-emails
# View scheduled tasks
php artisan schedule:list
Monitoring
Check Cron Job Status
In the admin panel, go to Settings > System to view:
Last scheduler run time
Cron job statistics
Number of invoices charged
Tasks completed per run
View Cron Job Logs
tail -f storage/logs/laravel.log | grep "Cronjob"
Database Statistics
Cron job statistics are stored in the cron_stats table:
SELECT * FROM cron_stats ORDER BY created_at DESC LIMIT 10 ;
Troubleshooting
Cron job not running
Verify crontab entry:
Check scheduler heartbeat:
php artisan tinker
>>> Setting::where( 'key' , 'last_cron_run' ) ->first ()- > value
Manually test:
php artisan schedule:run -vvv
Queue jobs not processing
Check if worker is running:
ps aux | grep "queue:work"
Check failed jobs:
Retry failed jobs:
php artisan queue:retry all
Tasks taking too long
Increase timeout for specific tasks:
Schedule :: command ( CronJob :: class )
-> dailyAt ( '00:00' )
-> timeout ( 300 ); // 5 minutes
Best Practices
Monitor cron job execution regularly to ensure invoices are being generated on time.
Set appropriate timeframes for suspension and termination
Review cron job statistics weekly
Keep queue workers monitored with Supervisor or systemd
Test changes to cron configuration in a staging environment
Configure email notifications for cron job failures
Regularly review and clean up old logs
Adjust timing based on your business hours and customer timezone
Email Notifications
Paymenter sends email notifications for critical events:
Invoice generated
Payment received
Service suspended
Service terminated
Payment failure (billing agreements)
Configure email templates in the admin panel under Settings > Email Templates.