prefix . 'woonoow_subscriptions'; // Check if table exists if ($wpdb->get_var("SHOW TABLES LIKE '$table'") !== $table) { SubscriptionManager::create_tables(); } } /** * Handle module enable */ public static function on_module_enabled($module_id) { if ($module_id === 'subscription') { SubscriptionManager::create_tables(); } } /** * Add subscription fields to product edit page */ public static function add_product_subscription_fields() { global $post; if (!ModuleRegistry::is_enabled('subscription')) { return; } echo '
'; woocommerce_wp_checkbox([ 'id' => '_woonoow_subscription_enabled', 'label' => __('Enable Subscription', 'woonoow'), 'description' => __('Enable recurring subscription billing for this product', 'woonoow'), ]); echo ''; // .woonoow-subscription-options // Add inline script to show/hide options based on checkbox ?> '; // .options_group } /** * Save subscription fields */ public static function save_product_subscription_fields($post_id) { $subscription_enabled = isset($_POST['_woonoow_subscription_enabled']) ? 'yes' : 'no'; update_post_meta($post_id, '_woonoow_subscription_enabled', $subscription_enabled); if (isset($_POST['_woonoow_subscription_period'])) { update_post_meta($post_id, '_woonoow_subscription_period', sanitize_text_field($_POST['_woonoow_subscription_period'])); } if (isset($_POST['_woonoow_subscription_interval'])) { update_post_meta($post_id, '_woonoow_subscription_interval', absint($_POST['_woonoow_subscription_interval'])); } if (isset($_POST['_woonoow_subscription_trial_days'])) { update_post_meta($post_id, '_woonoow_subscription_trial_days', absint($_POST['_woonoow_subscription_trial_days'])); } if (isset($_POST['_woonoow_subscription_signup_fee'])) { update_post_meta($post_id, '_woonoow_subscription_signup_fee', wc_format_decimal($_POST['_woonoow_subscription_signup_fee'])); } if (isset($_POST['_woonoow_subscription_length'])) { update_post_meta($post_id, '_woonoow_subscription_length', absint($_POST['_woonoow_subscription_length'])); } } /** * Maybe create subscription from completed order */ public static function maybe_create_subscription($order_id) { if (!ModuleRegistry::is_enabled('subscription')) { return; } $order = wc_get_order($order_id); if (!$order) { return; } // Check if subscription already created for this order if ($order->get_meta('_woonoow_subscription_created')) { return; } foreach ($order->get_items() as $item) { $product_id = $item->get_product_id(); $variation_id = $item->get_variation_id(); // Check if product has subscription enabled if (get_post_meta($product_id, '_woonoow_subscription_enabled', true) !== 'yes') { continue; } // Create subscription for this product SubscriptionManager::create_from_order($order, $item); } // Mark order as processed $order->update_meta_data('_woonoow_subscription_created', 'yes'); $order->save(); } /** * Modify add to cart button text for subscription products */ public static function subscription_add_to_cart_text($text, $product) { if (!ModuleRegistry::is_enabled('subscription')) { return $text; } $product_id = $product->get_id(); if (get_post_meta($product_id, '_woonoow_subscription_enabled', true) === 'yes') { // Guests cannot have subscriptions — create_from_order() rejects guest orders silently // (H6). Show the standard add-to-cart text and let the regular checkout flow handle // guest sign-up. The subscription will only be created if/when the customer converts // to a user. We do NOT advertise a subscription capability the system cannot honor. if (!is_user_logged_in()) { return $text; } $settings = ModuleRegistry::get_settings('subscription'); return $settings['button_text_subscribe'] ?? __('Subscribe Now', 'woonoow'); } return $text; } /** * Register subscription notification events * * @param array $events Existing events * @return array Updated events */ public static function register_notification_events($events) { // Customer notifications $events['subscription_pending_cancel'] = [ 'id' => 'subscription_pending_cancel', 'label' => __('Subscription Pending Cancellation', 'woonoow'), 'description' => __('When a subscription is scheduled for cancellation at period end', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_cancelled'] = [ 'id' => 'subscription_cancelled', 'label' => __('Subscription Cancelled', 'woonoow'), 'description' => __('When a subscription is cancelled and access ends', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_expired'] = [ 'id' => 'subscription_expired', 'label' => __('Subscription Expired', 'woonoow'), 'description' => __('When a subscription expires due to end date or failed payments', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_paused'] = [ 'id' => 'subscription_paused', 'label' => __('Subscription Paused', 'woonoow'), 'description' => __('When a subscription is put on hold', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_resumed'] = [ 'id' => 'subscription_resumed', 'label' => __('Subscription Resumed', 'woonoow'), 'description' => __('When a subscription is resumed from pause', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_renewal_failed'] = [ 'id' => 'subscription_renewal_failed', 'label' => __('Subscription Renewal Failed', 'woonoow'), 'description' => __('When a renewal payment fails', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_renewal_payment_due'] = [ 'id' => 'subscription_renewal_payment_due', 'label' => __('Subscription Renewal Payment Due', 'woonoow'), 'description' => __('When a manual payment is required for subscription renewal', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => array_merge(self::get_subscription_variables(), [ '{payment_link}' => __('Link to payment page', 'woonoow'), ]), ]; $events['subscription_renewal_reminder'] = [ 'id' => 'subscription_renewal_reminder', 'label' => __('Subscription Renewal Reminder', 'woonoow'), 'description' => __('Reminder before subscription renewal', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'customer', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; // Staff notifications $events['subscription_cancelled_admin'] = [ 'id' => 'subscription_cancelled', 'label' => __('Subscription Cancelled', 'woonoow'), 'description' => __('When a customer cancels their subscription', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'staff', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; $events['subscription_renewal_failed_admin'] = [ 'id' => 'subscription_renewal_failed', 'label' => __('Subscription Renewal Failed', 'woonoow'), 'description' => __('When a subscription renewal payment fails', 'woonoow'), 'category' => 'subscriptions', 'recipient_type' => 'staff', 'wc_email' => '', 'enabled' => true, 'variables' => self::get_subscription_variables(), ]; return $events; } /** * Get subscription-specific template variables * * @return array */ private static function get_subscription_variables() { return [ '{subscription_id}' => __('Subscription ID', 'woonoow'), '{subscription_status}' => __('Subscription status', 'woonoow'), '{product_name}' => __('Product name', 'woonoow'), '{billing_period}' => __('Billing period (e.g., monthly)', 'woonoow'), '{recurring_amount}' => __('Recurring payment amount', 'woonoow'), '{next_payment_date}' => __('Next payment date', 'woonoow'), '{end_date}' => __('Subscription end date', 'woonoow'), '{cancel_reason}' => __('Cancellation reason', 'woonoow'), ]; } /** * Handle pending cancellation notification */ public static function on_pending_cancel($subscription_id, $reason = '') { self::send_subscription_notification('subscription_pending_cancel', $subscription_id, $reason); } /** * Handle cancellation notification */ public static function on_cancelled($subscription_id, $reason = '') { self::send_subscription_notification('subscription_cancelled', $subscription_id, $reason); } /** * Handle expiration notification */ public static function on_expired($subscription_id, $reason = '') { self::send_subscription_notification('subscription_expired', $subscription_id, $reason); } /** * Handle pause notification */ public static function on_paused($subscription_id) { self::send_subscription_notification('subscription_paused', $subscription_id); } /** * Handle resume notification */ public static function on_resumed($subscription_id) { self::send_subscription_notification('subscription_resumed', $subscription_id); } /** * Handle renewal failed notification */ public static function on_renewal_failed($subscription_id, $failed_count) { self::send_subscription_notification('subscription_renewal_failed', $subscription_id, '', $failed_count); } /** * Handle renewal payment due notification */ public static function on_renewal_payment_due($subscription_id, $order = null) { $payment_link = ''; if ($order && is_a($order, 'WC_Order')) { $payment_link = $order->get_checkout_payment_url(); } self::send_subscription_notification('subscription_renewal_payment_due', $subscription_id, '', 0, ['payment_link' => $payment_link]); } /** * Handle renewal reminder notification */ public static function on_renewal_reminder($subscription) { if (!$subscription || !isset($subscription->id)) { return; } self::send_subscription_notification('subscription_renewal_reminder', $subscription->id); } /** * Send subscription notification * * @param string $event_id Event ID * @param int $subscription_id Subscription ID * @param string $reason Optional reason * @param int $failed_count Optional failed payment count * @param array $extra_data Optional extra data variables */ private static function send_subscription_notification($event_id, $subscription_id, $reason = '', $failed_count = 0, $extra_data = []) { $subscription = SubscriptionManager::get($subscription_id); if (!$subscription) { return; } $user = get_user_by('id', $subscription->user_id); $product = wc_get_product($subscription->product_id); $data = [ 'subscription' => $subscription, 'customer' => $user, 'product' => $product, 'reason' => $reason, 'failed_count' => $failed_count, 'payment_link' => $extra_data['payment_link'] ?? '', ]; // Send via NotificationManager if (class_exists('\\WooNooW\\Core\\Notifications\\NotificationManager')) { \WooNooW\Core\Notifications\NotificationManager::send($event_id, 'email', $data); } } /** * Handle order status changes for both parent and renewal orders */ public static function on_order_status_changed($order_id, $old_status, $new_status) { if (!ModuleRegistry::is_enabled('subscription')) { return; } global $wpdb; $table_orders = $wpdb->prefix . 'woonoow_subscription_orders'; // Find if this order is linked to any subscription $links = $wpdb->get_results($wpdb->prepare( "SELECT subscription_id, order_type FROM $table_orders WHERE order_id = %d", $order_id )); if (empty($links)) { return; } foreach ($links as $link) { $subscription = SubscriptionManager::get($link->subscription_id); if (!$subscription) continue; if ($link->order_type === 'renewal') { if (in_array($new_status, ['processing', 'completed'])) { $order = wc_get_order($order_id); if ($order) { SubscriptionManager::handle_renewal_success($link->subscription_id, $order); } } elseif ($new_status === 'failed') { SubscriptionManager::handle_renewal_failure($link->subscription_id); } elseif ($new_status === 'cancelled') { SubscriptionManager::update_status($link->subscription_id, 'cancelled', 'renewal_order_cancelled'); } elseif ($new_status === 'refunded') { SubscriptionManager::update_status($link->subscription_id, 'on-hold', 'renewal_order_refunded'); } } elseif ($link->order_type === 'parent') { if (in_array($new_status, ['refunded', 'cancelled'])) { SubscriptionManager::update_status($link->subscription_id, 'cancelled', 'parent_order_' . $new_status); } elseif ($new_status === 'on-hold') { SubscriptionManager::update_status($link->subscription_id, 'on-hold', 'parent_order_on_hold'); } } } } /** * Handle order trashing/deletion */ public static function on_order_deleted($order_id) { global $wpdb; $table_orders = $wpdb->prefix . 'woonoow_subscription_orders'; $links = $wpdb->get_results($wpdb->prepare( "SELECT subscription_id, order_type FROM $table_orders WHERE order_id = %d", $order_id )); foreach ($links as $link) { if ($link->order_type === 'parent') { SubscriptionManager::update_status($link->subscription_id, 'cancelled', 'parent_order_deleted'); } elseif ($link->order_type === 'renewal') { SubscriptionManager::update_status($link->subscription_id, 'on-hold', 'renewal_order_deleted'); } } } /** * Handle product trashing/deletion */ public static function on_post_deleted($post_id) { if (get_post_type($post_id) !== 'product') { return; } global $wpdb; $table_subs = $wpdb->prefix . 'woonoow_subscriptions'; // Find active/on-hold subscriptions for this product $affected = $wpdb->get_col($wpdb->prepare( "SELECT id FROM $table_subs WHERE product_id = %d AND status IN ('active', 'on-hold', 'pending')", $post_id )); foreach ($affected as $sub_id) { SubscriptionManager::update_status($sub_id, 'on-hold', 'product_deleted'); // Fire an action so merchants can hook in and get alerted do_action('woonoow/subscription/product_deleted_alert', $sub_id, $post_id); } } /** * Handle user deletion */ public static function on_user_deleted($user_id) { global $wpdb; $table_subs = $wpdb->prefix . 'woonoow_subscriptions'; $affected = $wpdb->get_col($wpdb->prepare( "SELECT id FROM $table_subs WHERE user_id = %d AND status != 'cancelled'", $user_id )); foreach ($affected as $sub_id) { SubscriptionManager::update_status($sub_id, 'cancelled', 'user_deleted'); } } }