Details
-
Type:
Bug
-
Status: Closed
-
Priority:
Blocker
-
Resolution: Fixed
-
Affects Version/s: 5.13.0
-
Fix Version/s: 5.13.1
-
Component/s: Staff Interface
-
Labels:None
Description
Recurring coupons are not applying to renewing services when the "Limitations do not apply to renewing services" option is enabled (limit_recurring = 0), even though they should continue to apply indefinitely regardless of expiration dates or usage limits.
Steps to Reproduce:
- Create a recurring coupon with start/end dates (e.g., 2025-12-31 to 2026-01-08)
- Set "Limitations do not apply to renewing services" (limit_recurring = 0)
- Assign the coupon to a service
- Wait until after the coupon's end date passes
- Attempt to renew the service
Expected Result:
The coupon should continue to apply to the service renewal because limitations (including expiration) are disabled for renewing services.
Actual Result:
The coupon does not apply after the end date has passed, even though limitations are disabled.
Root Cause:
Three interconnected bugs were introduced in version 5.13:
CORE-5499(commit 56c7390da, Oct 31 2025) added the limit_recurring option but didn't properly handle NULL date values when checking expiration- Commit c01688ef8 (Nov 5 2025) changed AbstractCoupon to "always check expiration regardless of limits" which broke the ignore limitations feature
- AbstractCoupon::expired() had inverted logic that treated NULL dates as expired instead of unlimited
Technical Details:
- Coupons::getRecurring() incorrectly evaluated NULL dates: toTime(null) returns false, causing $date > false to always be true
- AbstractCoupon::expired() returned true when dates were NULL (should return false - no expiration)
- AbstractCoupon::applies() always checked expiration even when limit_recurring = 0 (should skip expiration check)
Summary of the 3 updates across the 2 files in the current PR and how they relate:
1. coupons.php (Coupons::getRecurring())
- Method: getRecurring() - This method is ONLY called for recurring coupons
- Scenario: Recurring coupon with limit_recurring = '1' (limitations DO apply) AND NULL dates
- Bug: toTime(null) returns false, so $date > false is always true = coupon rejected
- Fix: Check if dates are not NULL before comparing
- Example: Recurring coupon with no end date set, but "Limitations do apply to renewing services" enabled
2. AbstractCoupon.php - expired() method (lines 370-371)
- Method: expired() - Called by the pricing system for all coupons
- Scenario: ANY coupon (recurring or not) with NULL dates
- Bug: Returned true (expired) if dates were NULL = coupon rejected
- Fix: NULL dates now correctly mean "no expiration"
- Example: Any coupon without start/end dates being treated as expired
3. AbstractCoupon.php - applies() method (line 168)
- Method: applies() - Main method that determines if coupon applies
- Scenario: Recurring coupon with limit_recurring = '0' (limitations DON'T apply) AND expired dates
- Bug: Always checked expiration even when limits shouldn't apply = customer's expired coupon rejected
- Fix: Skip expiration check for recurring coupons when limit_recurring = '0'
- Example: Customer reported expired coupon (2025-12-31) with "Limitations do not apply" enabled, coupon was skipped