Skip to content

Enforce max_uses_per_user in the standalone CouponValidator#2473

Merged
glennjacobs merged 1 commit into
1.xfrom
fix/coupon-validator-per-user-limit-2253
May 19, 2026
Merged

Enforce max_uses_per_user in the standalone CouponValidator#2473
glennjacobs merged 1 commit into
1.xfrom
fix/coupon-validator-per-user-limit-2253

Conversation

@glennjacobs
Copy link
Copy Markdown
Contributor

Summary

  • CouponValidator::validate() now also checks max_uses_per_user against the authenticated user when both are present.
  • Guests skip the per-user check (consistent with the cart-side fix from Coupon Applies but there is no effect on prices/calculations #2442).
  • Adds three tests covering: authenticated user at the limit, guest with another user at the limit, separate user under the limit.

Why

The standalone validator only checked the global max_uses count, while the cart-side path (AbstractDiscountType::checkDiscountConditions()) also enforces max_uses_per_user. That left the two entry points in conflict — Discounts::validateCoupon('FOO') or the ValidCoupon rule would return true for a user who had already hit their cap, while applying the same coupon via cart->update(['coupon_code' => 'FOO']) correctly rejected it.

The reporter calls this out as an abuse vector — front-end coupon-check endpoints that use the validator give the wrong answer.

Looking up auth()->user() inside the validator keeps the interface signature unchanged (validate(string $coupon): bool). Guests still pass because there is no per-user state to enforce.

Fixes #2253.

Test plan

  • vendor/bin/pest --testsuite=core — 506 passing
  • Manual: user with one redeemed use of a coupon → re-call Discounts::validateCoupon($coupon) → returns false

CouponValidator::validate() only checked the global max_uses count, so
a coupon validated through Discounts::validateCoupon() (or the
ValidCoupon rule) kept passing even after a user had hit their
per-user cap. The same coupon applied via cart->update() was rejected
correctly, leading to inconsistent behaviour between the two paths.

Look up the authenticated user via auth() and count their existing
uses on the matched discount when max_uses_per_user is set. Guests
still pass since per-user limits cannot be enforced without a user,
matching the cart-side behaviour fixed in #2442.

Fixes #2253
@glennjacobs glennjacobs merged commit 4df490d into 1.x May 19, 2026
16 checks passed
@glennjacobs glennjacobs deleted the fix/coupon-validator-per-user-limit-2253 branch May 19, 2026 08:55
@github-project-automation github-project-automation Bot moved this from Todo to Done in Roadmap May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Coupon‑per‑user limit not enforced in CouponValidator

2 participants