Hi!

We're using the 2.x branch with good success on the "standard" offer types, i.e. $ off, and % off... However, we are trying to offer free shipping based on a coupon code using the "Free Shipping" offer type, and it's not working. When the user tries to redeem the coupon they are provided a success message because the inline conditions worked, but the discount doesn't actually apply to the order.

Commerce Discount implements free shipping based on a rule that gets executed when shipping rates are collected (and in our case executes on a subsequent checkout step), and as a result the discount isn't valid until after shipping rates are collected. So when you add the coupon to the order you get a "success" message, but the coupon gets immediately removed because when commerce_coupon_commerce_cart_order_refresh gets called, it doesn't find the discount on the order and as a result it removes the coupon. On the shipping calculation screen the coupon isn't actually attached to the order, so the shipping rate alteration applied by commerce_discount doesn't work because the existence of the coupon code is a condition (as we want it to be), but it's not on the order because it was removed immediately after it was added.

My initial thought is just to disable the coupon cleanup that happens in commerce_coupon_commerce_cart_order_refresh, as that does fix the issue, i.e. the coupon adds to the order and the subsequent shipping calculation correctly offers a $0 shipping rate for the correct service as expected because the coupon exists on the order. But I'm trying to think of potential problems with this solution, as the cleanup was obviously implemented for a reason.

I also tried moving coupon redemption to a checkout step after the shipping method calculation, but since commerce_discount implements free shipping as an alteration to the actual generation of shipping rates (instead of as a line item that cancels out the cost of shipping), it doesn't work because the shipping rate has already been set.

So I'm not really sure if this is a "bug", but it's a bit of a "chicken-or-the-egg" scenario because free shipping doesn't work without the coupon, but the coupon doesn't work because the free shipping discount isn't added to the order yet. Given that the module acts like the coupon should apply and it never actually does, it at least seems like it could cause a lot of confusion for users, and it's also not possible to give free shipping with a coupon, which seems like a super useful ability.

Sorry, writing this after banging my head off this for a while, so this may not be entirely coherent... But any thoughts would be greatly appreciated :)

Comments

dpolant’s picture

The free shipping offer is indeed problematic because of the reasons you stated. Even within the context of Commerce Discount it doesn't function completely right - the conditions for it only fire when you hit the shipping page form because it uses the collect-rates event and does not do any integrity checks for itself on cart refresh.

I've implemented a partial fix: where Commerce Coupon will not remove free shipping coupons at order refresh. This should allow you to add coupons that enable free shipping.

Here's the hitch: if some one removes the coupon, it will not recalculate unless for some reason they go back to the shipping page. This could result in some one removing the discount on the review page and getting the free shipping without using the coupon. Perhaps a workaround is to take the "remove" button off the review page view ..

I'm going to pass off fixing the underlying issue with the free shipping offer to the discount issue queue. Coupon module is designed only to act as a "gate" for discounts when they check their conditions, and if an offer does not check its conditions regularly enough then there is not much we can do from here.

Committed to dev: http://drupalcode.org/project/commerce_coupon.git/commit/490f9c7

dpolant’s picture

Status: Active » Needs review
stockliasteroid’s picture

Wonderful, thank you! I was thinking along the same lines, but hadn't had a chance to look at implementation... I'll give this a try this week and report back. Thanks again!

mrP’s picture

@dpolant is there an existing issue with commerce_discount for the underlying problem?

stockliasteroid’s picture

Status: Needs review » Reviewed & tested by the community

Sorry for the delay in response, we're running this version now and it seems to work fine. Free shipping is still an odd exception, but commerce_coupon now handles it in an "expected" fashion, so I'm marking this RTBC.

guypaddock’s picture

Status: Reviewed & tested by the community » Needs work

Sorry, but this still needs work. This temporary solution works for the specific case of free shipping, but we're implementing another module that works with shipping discounts and it doesn't work there at all. Moreover, having to add exceptions for every kind of discount to this one area of the code isn't maintainable.

I'd suggest we add a hook here instead that gives modules a chance to control how the coupons are filtered, or gives them a chance to indicate whether or not the coupon still applies.

guypaddock’s picture

Status: Needs work » Needs review
StatusFileSize
new4.25 KB

Can someone try this patch on for size?

This is against the latest dev rather than stacking other changes made for this issue into the patch.

guypaddock’s picture

Ugh, digging deeper the real issue lies with commerce_coupon_order_coupon_code_discounts(). That function can only return line item discounts, rather than any generic discount on the order. As a result, free shipping discounts and all other types of discounts that don't directly affect line items for the cart require special handling.

Isn't there a way for the coupon module to store the coupons on the order instead of relying on line items?

guypaddock’s picture

Please disregard my patch in #7 and try this instead.

This patch removes the special case logic for free shipping from both commerce_coupon_commerce_cart_order_refresh() of commerce_coupon.module and render() in commerce_coupon_handler_field_discount_value_display.inc in favor of using the new hook I've introduced for coupon clean-up.

In addition, this patch fixes commerce_coupon_code_is_in_order() so that it actually works (I'm guessing this is an old hold-over from 1.x). This function is now used in commerce_coupon_order_coupon_code_discounts() to verify that the coupon actually exists on the order in question.

Please test and let me know if this works.

sureshpotnuru’s picture

Sorry, This patch not working in my side. Module enable and disable functioning works fine. No errors and warnings. But nothing happens what we expected functioning. Please let me know GuyPaddock. Thankq

guypaddock’s picture

When you say "nothing happens when we expected", can you elaborate? What workflow?

guypaddock’s picture

Just ran it through its paces and don't see a difference with the way free shipping is handled -- the patch doesn't appear to break that existing workflow.

Can you tell me what type of discount you're trying that's not working properly? Have you confirmed that the workflow works without the patch and fails with the patch?

sureshpotnuru’s picture

StatusFileSize
new92.96 KB

hi GuyPaddock,
After i am applying patch it works fine. no errors and warnings.It does not break the workflow also. My workflow is creating coupon for free shipping who entered coupon code they will have provided by shipping service free is applicable on order discount. Details shown in above image. Here i am using discount and coupon 2.x module. What i am expected means when user applied coupon code then he will get free shipping service only, other services will be disappeared in shipping service page. Is it possible with this patch. please let me know. Thankq.

guypaddock’s picture

Title: Free Shipping offer type » Coupon clean-up prevents the use of the Free Shipping offer type

Your use case is definitely unrelated to this patch; I just changed the issue title to help clarify this. This patch is only intent on fixing problems with the way that Commerce cleans-up coupons on the order when it doesn't think that that they apply any longer.

To do what you want, I see two options (you may have other options, but these are just the two I can think of right now):

  1. You could find or write a module that hides shipping methods when the free shipping discount applies to the order. (You may need to apply my patch if you're implementing your own type of coupon / offer to make this happen, but otherwise my patch doesn't apply here.)
  2. You could configure each of the conditions on the rules for the other shipping methods (under admin/commerce/config/shipping) so that conditions on the free shipping method only pass when the order has a free shipping discount, and the conditions on the other methods only pass where there is NOT a free shipping discount. Tedious if you have a lot of shipping methods, but it wouldn't require writing any code if I'm correct.
sureshpotnuru’s picture

hi GuyPaddock,
Thankyou for detailed justification. problem is solved with changing rules. It doesn't require any code. Follow second option by adding condition to the each of the shipping method and its working fine.

sureshpotnuru’s picture

Could you pls give clearly information about this patch. Thankq

mjgruta’s picture

Status: Needs review » Needs work

@GuyPaddock
Patch needs re-roll on the latest dev so we can test.

My issue is #2593251: "Unable to redeem coupon." when I use "% off of shipping"

joshmiller’s picture

Assigned: Unassigned » joshmiller

About to re-roll and test.

joshmiller’s picture

Version: 7.x-2.0-beta1 » 7.x-2.x-dev
Status: Needs work » Needs review
StatusFileSize
new8.71 KB

Updated patch attached. This was more than a re-roll because it interacted with some functions that no longer are included in commerce_coupon.

Note that free shipping coupons have worked since Dan posted the two hard-coded free-shipping conditions in this module. So the best outcome of this patch is to support free shipping coupons in a way that other modules can implement a similar "wait and see" if the order discount gets applied.

To test:

  1. Install Drupal + Commerce Kickstart 1.40 (or latest stable)
  2. Download Commerce Shipping, Commerce Flat Rate, Commerce Discount, Commerce Coupon
    • drush dl commerce_shipping commerce_flat_rate commerce_discount commerce_coupon-2.x
  3. Enable Modules
    • drush en commerce_shipping_ui commerce_flat_rate commerce_coupon -y
  4. /admin/commerce/config/shipping/services/flat-rate/add - Add an Expensive Flat Rate
  5. /admin/commerce/discounts/add - Add a Free Shipping Discount (make sure to add a coupon condition at the bottom!)
  6. Buy things and checkout!
  7. Without patch, I can add a coupon, select a shipping service, and in review the shipping service is free.
  8. With patch, I can do the same thing.

Please test and update the status as you have a chance!

joshmiller’s picture

Assigned: joshmiller » Unassigned
joshmiller’s picture

This patch also addresses percent_off_shipping and shipping_upgrade discount types. Thanks to #2593251: "Unable to redeem coupon." when I use "% off of shipping" for point out another kind of discount that fails. This patch makes all three kinds of shipping discounts work as coupons with one hook implementation.

michfuer’s picture

Coupons can reference multiple discounts. I would expect the coupon to be removed unless ALL of its discounts existed on the order. Currently that's not the case. Updated the patch to fix this. Also simplified commerce_coupon_cleanup_process_invoke().

The label for a free shipping discount was an empty string for me. It's because the discount isn't being detected in commerce_coupon_order_discount_ids(). Specifically this line

if (!empty($component['price']['data']['discount_name']))

I'm not sure if this is a commerce_discount issue or commerce_coupon because in commerce_discount_free_shipping_service() it's setting

$discount_price = array(
  'amount' => -$discount_amount,
  'currency_code' => $free_service_wrapper->commerce_unit_price->currency_code->value(),
  'data' => array(
    'discount_component_title' => check_plain($discount->component_title),
  ),
);

and that's what the component for my free shipping service looks like.

Also, I thought implementing the hook this patch creates to support all the shipping discount offers was incorrect because that should be the responsibility of commerce_shipping. Than I noticed that there's a lot of shipping support in both commerce_discount and commerce_coupon. Is there a legitimate reason it's done this way? Maybe there isn't a hook to define a new discount offer type?

torgospizza’s picture

Maybe there isn't a hook to define a new discount offer type?

There is, hook_commerce_discount_offer_type_info(). We're using it for a special discount offer type.

Free shipping discounts were something Ryan was just working on recently. You may want to look at #2560773: Add support for "Free or reduced shipping" offers.

EDIT to add, sorry if this is stuff you already know about :) Just wanted to mention it since I remember seeing Ryan's issues and patches for Free Shipping so recently!

torgospizza’s picture

BTW we're experiencing a problem related to this issue, so I'm testing the latest patch.

torgospizza’s picture

Status: Needs review » Needs work

Setting to needs work. Here are my findings:

- Getting an error "undefined variable: $result", probably because that var is returned outside of the condition if ($coupon->type == 'discount_coupon')
- Also pretty sure that in the function commerce_coupon_cleanup_process_invoke() we should specifically check if (!$eligible === FALSE) rather than if (!$eligible) because in the event that no modules return a value then $eligible may not be defined and this could throw more errors.

So unfortunately it seems like this patch did not solve anything for us, (having coupons removed during Checkout), but I do think the change itself is needed.

michfuer’s picture

Status: Needs work » Needs review
StatusFileSize
new7.63 KB
new1.64 KB

So I cleaned up commerce_coupon_commerce_coupon_cleanup_process() which should take of your undefined variable: $result error.

In commerce_coupon_cleanup_process_invoke() we foreach() over module_invoke_all() which returns an empty array() if no modules implement the hook. In PHP it's safe to foreach over an empty array().

Updated patch with interdiff provided.

jmsomera’s picture

Status: Needs review » Needs work

When using commerce_discount_date, validation is not working. When using commerce_coupon_date, validation is working.