Files
WooNooW/includes/Modules/Subscription/SubscriptionScheduler.php
2026-01-29 11:54:42 +07:00

264 lines
8.0 KiB
PHP

<?php
/**
* Subscription Scheduler
*
* Handles cron jobs for subscription renewals and expirations
*
* @package WooNooW\Modules\Subscription
*/
namespace WooNooW\Modules\Subscription;
if (!defined('ABSPATH')) exit;
use WooNooW\Core\ModuleRegistry;
class SubscriptionScheduler
{
/**
* Cron hook for processing renewals
*/
const RENEWAL_HOOK = 'woonoow_process_subscription_renewals';
/**
* Cron hook for checking expired subscriptions
*/
const EXPIRY_HOOK = 'woonoow_check_expired_subscriptions';
/**
* Cron hook for sending renewal reminders
*/
const REMINDER_HOOK = 'woonoow_send_renewal_reminders';
/**
* Initialize the scheduler
*/
public static function init()
{
// Register cron handlers
add_action(self::RENEWAL_HOOK, [__CLASS__, 'process_renewals']);
add_action(self::EXPIRY_HOOK, [__CLASS__, 'check_expirations']);
add_action(self::REMINDER_HOOK, [__CLASS__, 'send_reminders']);
// Schedule cron events if not already scheduled
self::schedule_events();
// Cleanup on plugin deactivation
register_deactivation_hook(WOONOOW_PLUGIN_FILE, [__CLASS__, 'unschedule_events']);
}
/**
* Schedule cron events
*/
public static function schedule_events()
{
if (!wp_next_scheduled(self::RENEWAL_HOOK)) {
wp_schedule_event(time(), 'hourly', self::RENEWAL_HOOK);
}
if (!wp_next_scheduled(self::EXPIRY_HOOK)) {
wp_schedule_event(time(), 'daily', self::EXPIRY_HOOK);
}
if (!wp_next_scheduled(self::REMINDER_HOOK)) {
wp_schedule_event(time(), 'daily', self::REMINDER_HOOK);
}
}
/**
* Unschedule cron events
*/
public static function unschedule_events()
{
wp_clear_scheduled_hook(self::RENEWAL_HOOK);
wp_clear_scheduled_hook(self::EXPIRY_HOOK);
wp_clear_scheduled_hook(self::REMINDER_HOOK);
}
/**
* Process due subscription renewals
*/
public static function process_renewals()
{
if (!ModuleRegistry::is_enabled('subscription')) {
return;
}
$due_subscriptions = SubscriptionManager::get_due_renewals();
foreach ($due_subscriptions as $subscription) {
// Log renewal attempt
do_action('woonoow/subscription/renewal_processing', $subscription->id);
try {
$success = SubscriptionManager::renew($subscription->id);
if ($success) {
do_action('woonoow/subscription/renewal_completed', $subscription->id);
} else {
// Auto-debit failed (returns false), so schedule retry
// Note: 'manual' falls into a separate bucket in SubscriptionManager and returns true (handled)
self::schedule_retry($subscription->id);
}
} catch (\Exception $e) {
// Log error
error_log('[WooNooW Subscription] Renewal failed for subscription #' . $subscription->id . ': ' . $e->getMessage());
do_action('woonoow/subscription/renewal_error', $subscription->id, $e);
// Also schedule retry on exception
self::schedule_retry($subscription->id);
}
}
}
/**
* Check for expired subscriptions
*/
public static function check_expirations()
{
global $wpdb;
if (!ModuleRegistry::is_enabled('subscription')) {
return;
}
$table = $wpdb->prefix . 'woonoow_subscriptions';
$now = current_time('mysql');
// Find subscriptions that have passed their end date
$expired = $wpdb->get_results($wpdb->prepare(
"SELECT id FROM $table
WHERE status = 'active'
AND end_date IS NOT NULL
AND end_date <= %s",
$now
));
foreach ($expired as $subscription) {
SubscriptionManager::update_status($subscription->id, 'expired');
do_action('woonoow/subscription/expired', $subscription->id, 'end_date_reached');
}
// Also check pending-cancel subscriptions that need to be finalized
$pending_cancel = $wpdb->get_results($wpdb->prepare(
"SELECT id FROM $table
WHERE status = 'pending-cancel'
AND next_payment_date IS NOT NULL
AND next_payment_date <= %s",
$now
));
foreach ($pending_cancel as $subscription) {
SubscriptionManager::update_status($subscription->id, 'cancelled');
do_action('woonoow/subscription/cancelled', $subscription->id, 'pending_cancel_completed');
}
}
/**
* Send renewal reminder emails
*/
public static function send_reminders()
{
global $wpdb;
if (!ModuleRegistry::is_enabled('subscription')) {
return;
}
// Check if reminders are enabled
$settings = ModuleRegistry::get_settings('subscription');
if (empty($settings['send_renewal_reminder'])) {
return;
}
$days_before = $settings['reminder_days_before'] ?? 3;
$reminder_date = date('Y-m-d H:i:s', strtotime("+$days_before days"));
$tomorrow = date('Y-m-d H:i:s', strtotime('+' . ($days_before + 1) . ' days'));
$table = $wpdb->prefix . 'woonoow_subscriptions';
// Find subscriptions due for reminder (that haven't had reminder sent for this billing cycle)
$due_reminders = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table
WHERE status = 'active'
AND next_payment_date IS NOT NULL
AND next_payment_date >= %s
AND next_payment_date < %s
AND (reminder_sent_at IS NULL OR reminder_sent_at < last_payment_date OR (last_payment_date IS NULL AND reminder_sent_at < start_date))",
$reminder_date,
$tomorrow
));
foreach ($due_reminders as $subscription) {
// Trigger reminder email
do_action('woonoow/subscription/renewal_reminder', $subscription);
// Mark reminder as sent in database
$wpdb->update(
$table,
['reminder_sent_at' => current_time('mysql')],
['id' => $subscription->id],
['%s'],
['%d']
);
}
}
/**
* Get retry schedule for failed payments
*
* @param int $subscription_id
* @return string|null Next retry datetime or null if no more retries
*/
public static function get_next_retry_date($subscription_id)
{
$subscription = SubscriptionManager::get($subscription_id);
if (!$subscription) {
return null;
}
$settings = ModuleRegistry::get_settings('subscription');
if (empty($settings['renewal_retry_enabled'])) {
return null;
}
$retry_days_str = $settings['renewal_retry_days'] ?? '1,3,5';
$retry_days = array_map('intval', array_filter(explode(',', $retry_days_str)));
$failed_count = $subscription->failed_payment_count;
if ($failed_count >= count($retry_days)) {
return null; // No more retries
}
$days_to_add = $retry_days[$failed_count] ?? 1;
return date('Y-m-d H:i:s', strtotime("+$days_to_add days"));
}
/**
* Schedule a retry for failed payment
*
* @param int $subscription_id
*/
public static function schedule_retry($subscription_id)
{
global $wpdb;
$next_retry = self::get_next_retry_date($subscription_id);
if ($next_retry) {
$table = $wpdb->prefix . 'woonoow_subscriptions';
$wpdb->update(
$table,
['next_payment_date' => $next_retry],
['id' => $subscription_id],
['%s'],
['%d']
);
}
}
}