get_row( $wpdb->prepare( 'SELECT * FROM ' . \Abovault\Model\Subscription::table() . ' WHERE id = %d', $subscriptionId ), ARRAY_A ); if ($sub && $sub['status'] === \Abovault\Model\Subscription::STATUS_PAST_DUE) { (new LifecycleProcessor())->retryOne($sub); } }); // Self-heal: ensure the recurring action exists even if activation was missed. add_action('init', static function (): void { if (function_exists('as_has_scheduled_action') && !as_has_scheduled_action(self::HOOK, [], self::GROUP)) { self::scheduleRecurring(); } }); } /** Schedule hourly. Idempotent. */ public static function scheduleRecurring(): void { if (!function_exists('as_schedule_recurring_action')) { return; // Action Scheduler not loaded yet; init hook will retry. } if (!as_has_scheduled_action(self::HOOK, [], self::GROUP)) { as_schedule_recurring_action(time() + 300, HOUR_IN_SECONDS, self::HOOK, [], self::GROUP); } } public static function unschedule(): void { if (function_exists('as_unschedule_all_actions')) { as_unschedule_all_actions(self::HOOK, [], self::GROUP); } } /** Entry point invoked by Action Scheduler each hour. */ public function run(): void { (new LifecycleProcessor())->process(); } }