Created
December 21, 2025 21:01
-
-
Save goranefbl/f222e6f1eada2209abdf10a6bdf11145 to your computer and use it in GitHub Desktop.
Woo Subscription Integration with WPGens Loyalty Plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * WooCommerce Subscription Integration for Points & Rewards | |
| * | |
| * Automatically applies available points as discount during subscription renewals | |
| * | |
| * @since 1.0.0 | |
| */ | |
| class WPGL_WooCommerce_Subscription | |
| { | |
| /** | |
| * Initialize the integration | |
| */ | |
| public static function init() | |
| { | |
| // Only proceed if WooCommerce Subscriptions is active | |
| if (!class_exists('WC_Subscriptions')) { | |
| return; | |
| } | |
| // Only proceed if points program is active | |
| if (!WPGL_Points_Core::is_active()) { | |
| return; | |
| } | |
| // Hook into subscription renewal order creation (action, not filter) | |
| add_action('wcs_renewal_order_created', [__CLASS__, 'apply_points_discount_on_renewal'], 10, 2); | |
| //WPGL_Logger::debug('[Renewal] Subscriptions integration: renewal handler registered.'); | |
| } | |
| /** | |
| * Apply points discount when subscription renewal order is created | |
| * | |
| * @param WC_Order $order The renewal order | |
| * @param WC_Subscription $subscription The subscription object | |
| * @return WC_Order Modified order | |
| */ | |
| public static function apply_points_discount_on_renewal($order, $subscription) | |
| { | |
| try { | |
| WPGL_Logger::order('[Renewal] Renewal created: evaluating points discount', [ | |
| 'order_id' => $order instanceof WC_Order ? $order->get_id() : null, | |
| 'subscription_id' => (is_object($subscription) && method_exists($subscription, 'get_id')) ? $subscription->get_id() : null, | |
| 'user_id' => $order instanceof WC_Order ? $order->get_user_id() : null, | |
| ]); | |
| // Guard against double-application | |
| if ($order->get_meta('_wpgens_loyalty_points_redeemed')) { | |
| WPGL_Logger::order('[Renewal] Skip: discount already applied to this order.', [ | |
| 'order_id' => $order->get_id(), | |
| ]); | |
| return $order; | |
| } | |
| // Check if auto-apply is enabled (moved here to avoid early WordPress loading issues) | |
| if (!WPGL_Points_Core::is_subscription_renewal_auto_apply_enabled()) { | |
| WPGL_Logger::order('[Renewal] Skip: auto-apply on renewal disabled in settings.', [ | |
| 'order_id' => $order->get_id(), | |
| ]); | |
| return $order; | |
| } | |
| $user_id = $order->get_user_id(); | |
| // Skip if no user ID | |
| if (!$user_id) { | |
| WPGL_Logger::order('[Renewal] Skip: renewal order has no user.', [ | |
| 'order_id' => $order->get_id(), | |
| ]); | |
| return $order; | |
| } | |
| // Get user's available points | |
| $available_points = WPGL_Database::get_user_points($user_id); | |
| // Skip if user has no points | |
| if ($available_points <= 0) { | |
| WPGL_Logger::points('[Renewal] Skip: user has no available points.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| ]); | |
| return $order; | |
| } | |
| // Get points settings | |
| $settings = WPGL_Points_Core::get_settings(); | |
| // Skip if conversion rate is not enabled | |
| if (!$settings['conversionRateEnabled']) { | |
| WPGL_Logger::points('[Renewal] Skip: conversion rate disabled.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| ]); | |
| return $order; | |
| } | |
| $conversion_rate = $settings['conversionRate']; | |
| $min_points = $conversion_rate['minPoints']; | |
| $max_points = $conversion_rate['maxPoints']; | |
| // Skip if user doesn't have minimum points required | |
| if ($available_points < $min_points) { | |
| WPGL_Logger::points('[Renewal] Skip: available points below minimum required.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| 'available_points' => $available_points, | |
| 'min_points' => $min_points, | |
| ]); | |
| return $order; | |
| } | |
| // Calculate order total for discount calculation | |
| $order_total = $order->get_total(); | |
| // Exclude shipping if configured | |
| if (!$conversion_rate['includeShipping']) { | |
| $order_total -= $order->get_total_shipping(); | |
| } | |
| // Skip if order total is 0 or negative | |
| if ($order_total <= 0) { | |
| WPGL_Logger::points('[Renewal] Skip: computed order_total for discount is non-positive.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| 'order_total' => $order_total, | |
| ]); | |
| return $order; | |
| } | |
| // Calculate maximum points we can use based on order total | |
| $points_per_currency_unit = $conversion_rate['points'] / $conversion_rate['value']; | |
| $max_points_for_order = ceil($order_total * $points_per_currency_unit); | |
| // Determine points to use (minimum of available points, max points setting, and max for order) | |
| $points_to_use = min($available_points, $max_points, $max_points_for_order); | |
| // Skip if no points can be used | |
| if ($points_to_use < $min_points) { | |
| WPGL_Logger::points('[Renewal] Skip: points_to_use below minimum after capping.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| 'points_to_use' => $points_to_use, | |
| 'min_points' => $min_points, | |
| ]); | |
| return $order; | |
| } | |
| // Calculate discount amount | |
| $discount_amount = ($points_to_use / $conversion_rate['points']) * $conversion_rate['value']; | |
| // Ensure discount doesn't exceed order total (for discount calculation) | |
| $discount_amount = min($discount_amount, $order_total); | |
| WPGL_Logger::points('[Renewal] Computed renewal discount from points.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| 'available_points' => $available_points, | |
| 'min_points' => $min_points, | |
| 'max_points' => $max_points, | |
| 'include_shipping' => !empty($conversion_rate['includeShipping']), | |
| 'order_total_for_discount' => $order_total, | |
| 'points_per_currency_unit' => $points_per_currency_unit, | |
| 'max_points_for_order' => $max_points_for_order, | |
| 'points_to_use' => $points_to_use, | |
| 'discount_amount' => $discount_amount, | |
| ]); | |
| // Apply the discount if there's an amount to discount | |
| if ($discount_amount > 0) { | |
| // Add negative fee to represent the points discount | |
| $fee = new WC_Order_Item_Fee(); | |
| $fee->set_name(__('Points discount', 'wpgens-loyalty-program')); | |
| $fee->set_amount(-$discount_amount); | |
| $fee->set_total(-$discount_amount); | |
| $fee->set_tax_status('none'); | |
| $order->add_item($fee); | |
| WPGL_Logger::order('[Renewal] Added negative fee for points discount.', [ | |
| 'order_id' => $order->get_id(), | |
| 'discount_amount' => $discount_amount, | |
| ]); | |
| // Recalculate order totals after adding the fee | |
| $order->calculate_totals(true); | |
| WPGL_Logger::order('[Renewal] Recalculated order totals after discount fee.', [ | |
| 'order_id' => $order->get_id(), | |
| 'new_total' => $order->get_total(), | |
| 'shipping_total' => $order->get_total_shipping(), | |
| ]); | |
| // Recalculate earnable points based on discounted totals | |
| $order->delete_meta_data('_wpgens_loyalty_points_amount'); | |
| $order->delete_meta_data('_wpgens_loyalty_points_rate'); | |
| $order->delete_meta_data('_wpgens_loyalty_points_awarded'); | |
| $order->save(); | |
| if (class_exists('WPGL_Points_Checkout')) { | |
| WPGL_Points_Checkout::calculate_order_points($order->get_id()); | |
| WPGL_Logger::order('[Renewal] Recalculated earnable points after discount fee.', [ | |
| 'order_id' => $order->get_id(), | |
| 'points_amount' => $order->get_meta('_wpgens_loyalty_points_amount') | |
| ]); | |
| } | |
| // Calculate actual points used based on final discount amount | |
| $actual_points_used = ($discount_amount / $conversion_rate['value']) * $conversion_rate['points']; | |
| $actual_points_used = ceil($actual_points_used); | |
| // Deduct points using the same action as checkout | |
| do_action( | |
| 'wpgens_loyalty_update_points', | |
| $user_id, | |
| -$actual_points_used, | |
| WPGL_Points_Activity_Type::DEDUCT, | |
| WPGL_Points_Source_Type::ORDER_DISCOUNT, | |
| $order->get_id(), | |
| sprintf( | |
| /* translators: %1$s: points used, %2$s: points label, %3$s: order ID */ | |
| __('Applied %1$s %2$s as discount on subscription renewal order #%3$s', 'wpgens-loyalty-program'), | |
| $actual_points_used, | |
| WPGL_Points_Core::get_points_label($actual_points_used), | |
| $order->get_id() | |
| ) | |
| ); | |
| WPGL_Logger::points('[Renewal] Deducted points for renewal discount.', [ | |
| 'order_id' => $order->get_id(), | |
| 'user_id' => $user_id, | |
| 'actual_points_used' => $actual_points_used, | |
| ]); | |
| // Store points info in order meta for tracking | |
| $order->update_meta_data('_wpgens_loyalty_points_redeemed', $actual_points_used); | |
| $order->update_meta_data('_wpgens_loyalty_points_discount_amount', $discount_amount); | |
| $order->update_meta_data('_wpgens_loyalty_points_user_id', $user_id); | |
| $order->update_meta_data('_wpgens_loyalty_source', 'subscription_renewal'); | |
| $order->save(); | |
| WPGL_Logger::order('[Renewal] Saved order meta for renewal discount.', [ | |
| 'order_id' => $order->get_id(), | |
| 'points_redeemed' => $actual_points_used, | |
| 'discount_amount' => $discount_amount, | |
| ]); | |
| // Add order note | |
| $order->add_order_note( | |
| sprintf( | |
| /* translators: %1$s: points used, %2$s: points label, %3$s: discount amount */ | |
| __('Applied %1$s %2$s as discount (-%3$s) during subscription renewal.', 'wpgens-loyalty-program'), | |
| $actual_points_used, | |
| WPGL_Points_Core::get_points_label($actual_points_used), | |
| wc_price($discount_amount) | |
| ) | |
| ); | |
| // Log the activity | |
| do_action('wpgens_points_applied_subscription_renewal', $user_id, $order->get_id(), $actual_points_used, $discount_amount); | |
| } | |
| } catch (Exception $e) { | |
| // Log error but don't break the renewal process | |
| WPGL_Logger::debug('WPGens Points & Rewards - [Renewal] Subscription renewal error: ' . $e->getMessage(), [ | |
| 'order_id' => $order instanceof WC_Order ? $order->get_id() : null, | |
| ]); | |
| } | |
| return $order; | |
| } | |
| /** | |
| * Create and apply virtual coupon to renewal order | |
| * | |
| * @param WC_Order $order The renewal order | |
| * @param float $discount_amount The discount amount | |
| * @param int $points_used The number of points used | |
| * @return string|false Coupon code on success, false on failure | |
| */ | |
| private static function apply_virtual_coupon_to_order($order, $discount_amount, $points_used) | |
| { | |
| try { | |
| // Replace virtual coupon approach with a negative fee on the order | |
| $fee = new WC_Order_Item_Fee(); | |
| $fee->set_name(__('Points discount', 'wpgens-loyalty-program')); | |
| $fee->set_amount(-$discount_amount); | |
| $fee->set_total(-$discount_amount); | |
| $fee->set_tax_status('none'); | |
| $order->add_item($fee); | |
| // Recalculate order totals | |
| $order->calculate_totals(true); | |
| // Return a placeholder string to keep existing calling code logic minimal | |
| return 'points_renewal_fee'; | |
| } catch (Exception $e) { | |
| WPGL_Logger::debug('WPGens Points & Rewards - [Renewal] Failed to apply renewal discount fee: ' . $e->getMessage()); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Check if WooCommerce Subscriptions is active | |
| * | |
| * @return bool | |
| */ | |
| public static function is_subscriptions_active() | |
| { | |
| return class_exists('WC_Subscriptions'); | |
| } | |
| /** | |
| * Get subscription renewal orders for a user (for debugging/admin purposes) | |
| * | |
| * @param int $user_id User ID | |
| * @param int $limit Number of orders to retrieve | |
| * @return array Array of renewal orders | |
| */ | |
| public static function get_user_renewal_orders($user_id, $limit = 10) | |
| { | |
| if (!self::is_subscriptions_active()) { | |
| return []; | |
| } | |
| $args = [ | |
| 'customer_id' => $user_id, | |
| 'type' => 'shop_order', | |
| 'status' => 'any', | |
| 'limit' => $limit, | |
| 'meta_query' => [ | |
| [ | |
| 'key' => '_subscription_renewal', | |
| 'compare' => 'EXISTS' | |
| ] | |
| ] | |
| ]; | |
| $orders = wc_get_orders($args); | |
| return $orders; | |
| } | |
| } | |
| // Initialize the integration | |
| add_action('plugins_loaded', ['WPGL_WooCommerce_Subscription', 'init'], 20); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment