finalizing subscription moduile, ready to test
This commit is contained in:
563
includes/Modules/Subscription/SubscriptionModule.php
Normal file
563
includes/Modules/Subscription/SubscriptionModule.php
Normal file
@@ -0,0 +1,563 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Subscription Module Bootstrap
|
||||
*
|
||||
* @package WooNooW\Modules\Subscription
|
||||
*/
|
||||
|
||||
namespace WooNooW\Modules\Subscription;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
use WooNooW\Core\ModuleRegistry;
|
||||
use WooNooW\Modules\SubscriptionSettings;
|
||||
|
||||
class SubscriptionModule
|
||||
{
|
||||
|
||||
/**
|
||||
* Initialize the subscription module
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
// Register settings schema
|
||||
SubscriptionSettings::init();
|
||||
|
||||
// Initialize manager immediately since we're already in plugins_loaded
|
||||
self::maybe_init_manager();
|
||||
|
||||
// Install tables on module enable
|
||||
add_action('woonoow/module/enabled', [__CLASS__, 'on_module_enabled']);
|
||||
|
||||
// Add product meta fields
|
||||
add_action('woocommerce_product_options_general_product_data', [__CLASS__, 'add_product_subscription_fields']);
|
||||
add_action('woocommerce_process_product_meta', [__CLASS__, 'save_product_subscription_fields']);
|
||||
|
||||
// Hook into order completion to create subscriptions
|
||||
add_action('woocommerce_order_status_completed', [__CLASS__, 'maybe_create_subscription'], 10, 1);
|
||||
add_action('woocommerce_order_status_processing', [__CLASS__, 'maybe_create_subscription'], 10, 1);
|
||||
|
||||
// Hook into order status change to handle manual renewal payments
|
||||
add_action('woocommerce_order_status_changed', [__CLASS__, 'on_order_status_changed'], 10, 3);
|
||||
|
||||
// Modify add to cart button text for subscription products
|
||||
add_filter('woocommerce_product_single_add_to_cart_text', [__CLASS__, 'subscription_add_to_cart_text'], 10, 2);
|
||||
add_filter('woocommerce_product_add_to_cart_text', [__CLASS__, 'subscription_add_to_cart_text'], 10, 2);
|
||||
|
||||
// Register subscription notification events
|
||||
add_filter('woonoow_notification_events_registry', [__CLASS__, 'register_notification_events']);
|
||||
|
||||
// Hook subscription lifecycle events to send notifications
|
||||
add_action('woonoow/subscription/pending_cancel', [__CLASS__, 'on_pending_cancel'], 10, 2);
|
||||
add_action('woonoow/subscription/cancelled', [__CLASS__, 'on_cancelled'], 10, 2);
|
||||
add_action('woonoow/subscription/expired', [__CLASS__, 'on_expired'], 10, 2);
|
||||
add_action('woonoow/subscription/paused', [__CLASS__, 'on_paused'], 10, 1);
|
||||
add_action('woonoow/subscription/resumed', [__CLASS__, 'on_resumed'], 10, 1);
|
||||
add_action('woonoow/subscription/renewal_failed', [__CLASS__, 'on_renewal_failed'], 10, 2);
|
||||
add_action('woonoow/subscription/renewal_payment_due', [__CLASS__, 'on_renewal_payment_due'], 10, 2);
|
||||
add_action('woonoow/subscription/renewal_reminder', [__CLASS__, 'on_renewal_reminder'], 10, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize manager if module is enabled
|
||||
*/
|
||||
public static function maybe_init_manager()
|
||||
{
|
||||
if (ModuleRegistry::is_enabled('subscription')) {
|
||||
// Ensure tables exist
|
||||
self::ensure_tables();
|
||||
SubscriptionManager::init();
|
||||
SubscriptionScheduler::init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure database tables exist
|
||||
*/
|
||||
private static function ensure_tables()
|
||||
{
|
||||
global $wpdb;
|
||||
$table = $wpdb->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 '<div class="options_group show_if_simple show_if_variable">';
|
||||
|
||||
woocommerce_wp_checkbox([
|
||||
'id' => '_woonoow_subscription_enabled',
|
||||
'label' => __('Enable Subscription', 'woonoow'),
|
||||
'description' => __('Enable recurring subscription billing for this product', 'woonoow'),
|
||||
]);
|
||||
|
||||
echo '<div class="woonoow-subscription-options" style="display:none;">';
|
||||
|
||||
woocommerce_wp_select([
|
||||
'id' => '_woonoow_subscription_period',
|
||||
'label' => __('Billing Period', 'woonoow'),
|
||||
'description' => __('How often to bill the customer', 'woonoow'),
|
||||
'options' => [
|
||||
'day' => __('Daily', 'woonoow'),
|
||||
'week' => __('Weekly', 'woonoow'),
|
||||
'month' => __('Monthly', 'woonoow'),
|
||||
'year' => __('Yearly', 'woonoow'),
|
||||
],
|
||||
'value' => get_post_meta($post->ID, '_woonoow_subscription_period', true) ?: 'month',
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_subscription_interval',
|
||||
'label' => __('Billing Interval', 'woonoow'),
|
||||
'description' => __('Bill every X periods (e.g., 2 = every 2 months)', 'woonoow'),
|
||||
'type' => 'number',
|
||||
'value' => get_post_meta($post->ID, '_woonoow_subscription_interval', true) ?: 1,
|
||||
'custom_attributes' => [
|
||||
'min' => '1',
|
||||
'max' => '365',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_subscription_trial_days',
|
||||
'label' => __('Free Trial Days', 'woonoow'),
|
||||
'description' => __('Number of free trial days before first billing (0 = no trial)', 'woonoow'),
|
||||
'type' => 'number',
|
||||
'value' => get_post_meta($post->ID, '_woonoow_subscription_trial_days', true) ?: 0,
|
||||
'custom_attributes' => [
|
||||
'min' => '0',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_subscription_signup_fee',
|
||||
'label' => __('Sign-up Fee', 'woonoow') . ' (' . get_woocommerce_currency_symbol() . ')',
|
||||
'description' => __('One-time fee charged on first subscription order', 'woonoow'),
|
||||
'type' => 'text',
|
||||
'value' => get_post_meta($post->ID, '_woonoow_subscription_signup_fee', true) ?: '',
|
||||
'data_type' => 'price',
|
||||
]);
|
||||
|
||||
woocommerce_wp_text_input([
|
||||
'id' => '_woonoow_subscription_length',
|
||||
'label' => __('Subscription Length', 'woonoow'),
|
||||
'description' => __('Number of billing periods (0 = unlimited/until cancelled)', 'woonoow'),
|
||||
'type' => 'number',
|
||||
'value' => get_post_meta($post->ID, '_woonoow_subscription_length', true) ?: 0,
|
||||
'custom_attributes' => [
|
||||
'min' => '0',
|
||||
'step' => '1',
|
||||
],
|
||||
]);
|
||||
|
||||
echo '</div>'; // .woonoow-subscription-options
|
||||
|
||||
// Add inline script to show/hide options based on checkbox
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
jQuery(function($) {
|
||||
function toggleSubscriptionOptions() {
|
||||
if ($('#_woonoow_subscription_enabled').is(':checked')) {
|
||||
$('.woonoow-subscription-options').show();
|
||||
} else {
|
||||
$('.woonoow-subscription-options').hide();
|
||||
}
|
||||
}
|
||||
toggleSubscriptionOptions();
|
||||
$('#_woonoow_subscription_enabled').on('change', toggleSubscriptionOptions);
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
||||
echo '</div>'; // .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') {
|
||||
$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 manual renewal payment completion
|
||||
*/
|
||||
public static function on_order_status_changed($order_id, $old_status, $new_status)
|
||||
{
|
||||
if (!ModuleRegistry::is_enabled('subscription')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array($new_status, ['processing', 'completed'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a subscription renewal order
|
||||
global $wpdb;
|
||||
$table_orders = $wpdb->prefix . 'woonoow_subscription_orders';
|
||||
|
||||
$link = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT subscription_id, order_type FROM $table_orders WHERE order_id = %d",
|
||||
$order_id
|
||||
));
|
||||
|
||||
if ($link && $link->order_type === 'renewal') {
|
||||
$order = wc_get_order($order_id);
|
||||
if ($order) {
|
||||
SubscriptionManager::handle_renewal_success($link->subscription_id, $order);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user