Skip to content

Instantly share code, notes, and snippets.

@goranefbl
Created December 21, 2025 21:01
Show Gist options
  • Select an option

  • Save goranefbl/f222e6f1eada2209abdf10a6bdf11145 to your computer and use it in GitHub Desktop.

Select an option

Save goranefbl/f222e6f1eada2209abdf10a6bdf11145 to your computer and use it in GitHub Desktop.
Woo Subscription Integration with WPGens Loyalty Plugin
<?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