Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
When applying a coupon through the coupon redemption form we can check the coupon availability. However, it is hard to do this over an API context or to check if a coupon is still available for an order.
In \Drupal\commerce_promotion\Plugin\Commerce\InlineForm\CouponRedemption::validateInlineForm we have:
if (!$coupon->available($order)) {
$form_state->setErrorByName($coupon_code_path, t('The provided coupon code is invalid.'));
return;
}
if (!$coupon->getPromotion()->applies($order)) {
$form_state->setErrorByName($coupon_code_path, t('The provided coupon code is invalid.'));
return;
}
And in \Drupal\commerce_promotion\PromotionOrderProcessor::process we have:
if ($coupon->available($order) && $promotion->applies($order)) {
$promotion->apply($order);
}
else {
// The promotion is no longer available (end date, usage, etc).
$order->get('coupons')->removeItem($index);
}
There isn't an easy way to validate coupons on the order.
Comment | File | Size | Author |
---|---|---|---|
#33 | 3041856-followup2.patch | 6.64 KB | mglaman |
| |||
#32 | 3041856-followup.patch | 6.64 KB | mglaman |
#28 | 3041856-28.patch | 11.57 KB | mglaman |
| |||
#25 | 3041856-25.patch | 11.63 KB | mglaman |
| |||
#25 | interdiff-3041856-22-25.txt | 3.18 KB | mglaman |
Comments
Comment #2
mglamanThis adds a constraint to the coupon field which validates if the coupon and promotion apply. If it does not, then a constraint is returned.
This allows validating an order to see if coupons apply or not without blindly applying them and hope they persist, and trying to catch if they'll be removed on the next order refresh.
Comment #3
bojanz CreditAttribution: bojanz at Centarro commentedLooks good!
Super confused by this though.
Comment #4
mglamanI think it's just a check I added in case someone attached it on an incorrect base field. I shouldn't do that, just add an
assert
Comment #5
mglamanRemoved that for an assert
Comment #6
bojanz CreditAttribution: bojanz at Centarro commented#5 is not a patch for this issue.
Also, the validator currently checks coupon availability even if the order has already been placed, which is incorrect.
Comment #7
mglamanWhoops, sorry. Too much multitasking.
Here is a proper patch and it covers placed orders.
Comment #8
mglamanI don't know how to upload patches.
Comment #9
mglamanThe next step here is to 👍 the approach and replace the two areas highlighted in the issue summary to run validation against the field and set the error versus running the logic separately.
Comment #10
bojanz CreditAttribution: bojanz at Centarro commentedI would prefer that the inline form stays as-is. It is cleaner.
PromotionOrderProcessor should be converted.
Comment #11
mglamanSounds good, and makes sense - why add it to the order to run validation in the inline form when it's available right there. I'll update PromotionOrderProcessor. This made me realize we have no context as to what code.
In the entity reference constraint code I saw we can specify the field item delta using this snippet:
Comment #12
mglamanHere is an updated patch. Turns out PromotionOrderProcessorTest never tested the removal of coupons. I added that to test the changes worked as expected.
Comment #13
tofasuriawanI faced an issue after applying patch #12, my local installation showing error Unable to remove item at non-existing index. on this line:
$order->get('coupons')->removeItem($constraint->getPropertyPath());
This happened after I delete applied coupon code in promotion setting without removing coupon in order first.
Step to reproduce the issue:
Comment #14
mglamanOh, interesting! That was not considered. Thanks.
Comment #15
tofasuriawanAfter further checking,
removeItem()
expect an integer parameter, but get string fromgetPropertyPath()
like'0.target_id'
or'1.target_id'
which is not an index on the coupon list.My solution is convert
$constraint->getPropertyPath()
to integer then applyremoveItem()
. Mine works, but I am not sure that is a good approach. I was wondering if you would give some suggestions. Thank youComment #16
mglamanComing back around to this.
Comment #17
mglamanThe error was due to constraints existing from ValidReference
It sets the property path to the field properties of target_id or entity
Comment #18
mglamanThis addresses #13. The coupon constraint now has the same path setup as invalid reference constraints. As a bonus, this order processor now ensures deleted coupons are removed from the order.
Comment #19
mglamanFix the property path in the assertion.
Comment #20
mglamanTests are no longer passing.
Comment #21
mglamanSwitched the kernel test to OrderKernelTestBase for schema fix.
Comment #22
mglamanFix setUp against new base test class.
Comment #23
jsacksick CreditAttribution: jsacksick at Centarro commentedI haven't manually tested the patch, but it looks really good! I probably would have picked a different name for the constraint plugin (Something like "CouponValidConstraint" but it matches the
available()
method on the coupon class, so I think this makes sense, though we could have named it "CouponAvailabilityConstraint" as well.Comment #24
mglamanAs bojanz pointed out, currently the redemption form uses this wording
Lets use that.
Comment #25
mglamanAdjusted the wording.
Comment #26
mglamanThe one thing I wanted to double check was this line. Core uses this as well in ValidReferenceConstraintValidator.
Comment #27
mglamanbojanz pointed out the verbs are all over the place.
This checks available & applies on coupons. So it should be called CouponValid.
Comment #28
mglamans/CouponAvailable/CouponValid
Comment #29
mglaman🥳 merged.
Comment #30
bojanz CreditAttribution: bojanz at Centarro commentedLet's do a followup.
Can we put a one-line comment above this block to explain why we're doing this?
Something like "Remove coupons which are no longer valid (availability/conditions)."
I would expect a test for CouponValidConstraintValidator to have a @coversDefaultClass pointing to it, and the test not to need the full order refresh.
Doesn't this belong in PromotionOrderProcessorTest, since that's where the removal is done?
Comment #31
mglamanI can add the covers annotations. With
testCouponRemoval
moved toPromotionOrderProcessorTest
there's no refresh needed.That exists so that the store_id constraint doesn't fire, because of entity access checks. And also for recording promotion usage.
Comment #32
mglamanFollow up. Should be OK but want DrupalCI to verify.
Comment #33
mglaman🤦♂️
Comment #35
mglamanCommitted the follow up