Honour discount conditions in BuyXGetY - #2624
Merged
glennjacobs merged 2 commits intoAug 25, 2026
Merged
Conversation
AbstractDiscountType::checkDiscountConditions() enforces a discount's minimum spend, customer restrictions and per-user usage limit. AmountOff::apply() guards on it before doing anything; BuyXGetY::apply() never calls it, so a BuyXGetY discount applies whenever its quantity condition is met, whatever those fields say. Dates, max_uses, the coupon and the channel are already enforced by the query in DiscountManager::getDiscounts(), so those continue to hold. What is only checked in checkDiscountConditions() is ignored: a "spend £50, get one free" promotion fires on a £20 cart, and a discount restricted to named customers applies to everyone. Adds the same guard AmountOff uses. Tests cover a cart below the advertised minimum spend and a discount restricted to a customer the cart does not belong to; both fail on 1.x with "Failed asserting that 1000 matches expected 0". A third asserts the discount still applies when the minimum is met, so the guard is not simply switching it off. Core suite: 556 passing before, 559 after.
glennjacobs
approved these changes
Aug 25, 2026
This was referenced Aug 26, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A merchant sets up "spend £50, get one free" and it fires on a £20 cart. A
discount restricted to named customers applies to everyone.
What happens now
at any cart value.
max_uses_per_useris likewise not consulted.min_qty) is the only thing actually gating it.What should happen
AmountOff.Why
AbstractDiscountType::checkDiscountConditions()enforces minimum spend,customer restrictions and
max_uses_per_user.AmountOff::apply()guards on itfirst thing:
BuyXGetY::apply()has no such call — the method is never referenced in theclass.
Worth noting what is not affected, since it narrows the blast radius: dates,
max_uses, the coupon and the channel are all enforced by the query inDiscountManager::getDiscounts()(active(),usable(), and the couponwhen()clause), so those keep working today. Only the three conditions checkedsolely inside
checkDiscountConditions()are being skipped.The fix
Add the same guard
AmountOffalready uses, at the top ofBuyXGetY::apply().Nothing else in the class changes, and no condition logic is duplicated — it
routes through the shared method so the two discount types stay consistent.
One interaction worth flagging: this means BuyXGetY now also inherits the
guest-cart behaviour of
max_uses_per_userthat #2470 is addressing. That seemsright — it is the same rule
AmountOffalready follows — but if #2470 landsfirst, both types will pick up its fix together.
Tests
tests/core/Unit/DiscountTypes/BuyXGetYConditionsTest.php:£20 cart. Fails on
1.xwithFailed asserting that 1000 matches expected 0.failure on
1.x.so the guard is not simply switching the discount off.