When apply coupon in cart page and go to checkout page the coupon removed and the total back to the old total (without coupon).

Comments

Ahmad Abbad’s picture

Title: Apply Coupon in cart page not effect in total in checkout page(coupon remove in checkout page) » Apply Coupon in cart page not effect in total amount in checkout page(the coupon is removed in checkout page)
torgosPizza’s picture

torgosPizza’s picture

It is definitely Coupon's implementation of cart_order_refresh() that is the culprit. I added a debug_backtrace and it's clearly happening when this method gets called, but I'm trying to figure out how or why all of those conditions would be satisfied. This is the function (with my added debugging functions):

foreach ($order_wrapper->commerce_coupons->value() as $coupon) {
    if ($coupon && $coupon->type == 'discount_coupon' && !commerce_coupon_order_coupon_code_discounts($coupon->code, $order) && !_commerce_coupon_free_shipping_single_discount($coupon)) {
      // Remove invalid coupons.
      watchdog('commerce_coupon', t('Coupon :code met conditions for removal.', array(':code' => $coupon->code)));
      $order->log = t('Removing coupon :code because conditions were met in cart_order_refresh. ', array(':code' => $coupon->code));
      commerce_coupon_remove_coupon_from_order($order, $coupon, FALSE);

      $error = &drupal_static('commerce_coupon_error_' . strtolower($coupon->code));
      if (!$error) {
        // Set a generic error message unless something has
        $error = t('Unable to redeem coupon.');
      }
    }
  }

I looked at the function _commerce_coupon_free_shipping_single_discount() and it tends to return NULL, which is then evaluated with a negated condition. I'm not sure but perhaps there is a funkiness with PHP 5.5 that interprets "NOT NULL" as true? The other condition function returns an empty array, which is (I think) generally a Drupal best practice, or at least one that's easier to read than the current method.

@dpolant: Any thoughts? I'm confirming that it is these conditions are somehow met, one time out of ten (or sometimes fewer).

EDIT: I'm going to try this condition next, checking for FALSE specifically.

if ($coupon && $coupon->type == 'discount_coupon' && 
      !commerce_coupon_order_coupon_code_discounts($coupon->code, $order) && 
      _commerce_coupon_free_shipping_single_discount($coupon) === FALSE) {
torgosPizza’s picture

It turns out the actual condition that was returning what it shouldn't was this:

!commerce_coupon_order_coupon_code_discounts($coupon->code, $order) 

For some reason, the field $coupon_wrapper->commerce_discount_reference->raw() would return as empty. Looking at the "Discount reference" field on the Discount Coupon type's settings page, it looks like that field is set to allow multiple values. I'm not sure if this is something custom to mine or not, because I can no longer edit the field values.

In any case, for whatever reason the function is trying to load an empty array into entity_load_multiple (to load and return the discounts if any are found). When the empty value is passed into those conditions it renders as TRUE (coupon is invalid because it does not have any discounts associated with it).

As a temporary fix, I went into the coupon type's management page for its fields, edited the Discount reference, and re-saved it. So far I do not see any coupons being removed from orders but I will let the logs stream for a bit to know for sure.

EDIT: It also looks like that old coupons still get loaded and conditions executed, if you have disabled a Discount but not also disabled the associated Coupon. I'm not sure if this is related but seems like possibly another bug anyway.

torgosPizza’s picture

Digging more into our watchdog logs, it seems that this normally happens in two places:

1) during the call to "system/ajax" after the "Add coupon to order" button is pressed, or
2) loading the Review stage.

It also seems whenever there is a discount coupon that fails, two things are observed:
1) The discount_ids and condition arguments sent to commerce_coupon_discount_load_multiple() are both empty;
2) because array_intersect() on discount_reference->raw() and discount_ids returns an empty string (" ")

At this point I'm not sure if the root cause is actually Coupons, or if it's deeper integration for instance the Entityreference module or some other form of field caching-related problems. One issue I am looking at testing is #1969018: Field schema foreign keys is always hard-coded to 'node' despite target type not set to node entity type though I'm not sure of how responsible is Entityreference vs. the Entity API (which we use to extract values like the commerce_discount_reference->value() for doing array_intersect for finding a coupon's discounts) since I haven't really a great grasp of the nuts and bolts of those systems.

I'm trying the patch from that issue to see if it has any effect.

torgosPizza’s picture

torgosPizza’s picture

I got a related error when I ran the latest Entityreference update (version 7003):
Notice: Undefined index: in commerce_discount_default_rules_configuration() (line 26 of commerce_discount/commerce_discount.rules_defaults.inc)

That code looks like this (line 26 is where it checks for getBundle()):

function commerce_discount_default_rules_configuration() {
  $types = commerce_discount_types();
  $offer_types = commerce_discount_offer_types();

  $rules = array();

  // Create a rule for each commerce discount entity.
  entity_get_controller('commerce_discount')->resetCache();

  foreach (entity_load('commerce_discount') as $discount) {
    $wrapper = entity_metadata_wrapper('commerce_discount', $discount);
    $wrapper_properties = $wrapper->getPropertyInfo();
    // Only for Commerce Discount wrappers with Commerce Discount Offer defined.
    if (in_array('commerce_discount_offer', array_keys($wrapper_properties))) {
      $type = $types[$discount->type];
      $offer_type = $offer_types[$wrapper->commerce_discount_offer->getBundle()]; // This value is empty.
...(snip)

Digging in, the Rule that was auto-created for this broken discount was marked dirty, with the note "Unknown action .". So, somehow, the Discount Offer (which was a percentage off discount) had lost its relationship to the Discount.

Going deeper I dpm'd the CommerceDiscount object, and the target_id for the Discount Offer in field commerce_discount_offer is 756... but when I look in the {commerce_discount} table, there is no associated row for that discount ID. It's like the row was either deleted or changed, but I can't imagine why.

EDIT: There is an issue regarding this type of bug at #2299013: Undefined index: in commerce_discount_default_rules_configuration() when update or cron is run

One thing I discovered also, is that you can edit the dirty Rule itself at admin/config/workflow/rules - Delete the "Unknown Action" and then re-add the correct Discount Offer - and then it appears to work fine. But if you REVERT the Rule back to its default state, the default rules get rebuilt, and once again the relationship in the field is broken.

I will continue to dig but so far my money is on something wonky going on with the commerce_discount_offer field, and/or the field being "seen" in places it shouldn't (for instance, Gift Card Coupons) causing all kinds of havoc with the Order Refresh system (including the line items "out of sync" issue described in #2328357: Discount amount goes away when checkout completed).

torgosPizza’s picture

Title: Apply Coupon in cart page not effect in total amount in checkout page(the coupon is removed in checkout page) » Coupon is removed during Checkout
torgosPizza’s picture

Version: 7.x-2.0-beta3 » 7.x-2.x-dev
Ahmad Abbad’s picture

Thanks torgosPizza ,
i have solved this issue by create new rule when the amount be zero go to checkout complete page and skip payment tap

torgosPizza’s picture

Status: Active » Fixed

Our issue ended up being that the order was being refreshed by external services pinging the checkout URLs - for example if a user is using Chrome and they hit a checkout/ORDER-ID link, moments afterward a social sharing widget would ping that URL to see if it was valid/shareable. Due to our permissions settings, we had it setup so that Anon users could access checkout (for Checkout Redirect, where they are required to login) but Anon users could not redeem coupons.

I did more digging and found that even if Anonymous users CAN'T access checkout, because of the way Drupal's menu object autoloading classes (such as checkout/%commerce_order) works, the full order is loaded and refreshed before returning Access Denied. Thus, due to our combination of settings, the order was refreshed and the coupon was removed by the anonymous user.

Our solution there was to just allow Anony users to "redeem coupons". Since then we haven't seen this issue pop up at all, after several subsequent coupon promotions. So for me this issue is FIXED - just make sure Permissions are configured as you might expect.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

torgosPizza’s picture

Status: Closed (fixed) » Needs work
Issue tags: -commerce_coupon commerce_discount commerce coupons commerce_line_items +commerce-sprint
Related issues: +#2263315: Allow discount_coupon on orders without the discount, +#2328357: Discount amount goes away when checkout completed

Tagging for sprint. Reopening because this problem still exists and is reproducible.

I'm fairly certain now that the issue is a race condition of some type. Essentially during a cart_order_refresh, the Discounts implementation removes all discount price components from a line item, but the Coupons module inspects the price components on a line item to determine if a product discount has been applied. This creates a bit of circular logic during a race condition if the Coupons module hasn't had a chance to re-add its price components before the order is refreshed and the components are inspected. (Did everyone follow all that?)

The issue #2263315: Allow discount_coupon on orders without the discount has one solution, but to me it's a bit of a workaround and doesn't really address the root issue.

torgosPizza’s picture

Confirmed from the other issue that this appears to have been caused by #1268472: Recursion in price calculation causes cart to skip calculation rules but I still think the logic is a tad bit circular. (This may be why the OP was able to fix this issue but calling an additional commerce_cart_order_refresh() during the complete_order() hook.

I'm not sure we can always ensure that the line items will have discount price components during the Discount cart refresh, so we should either a) remove that check, since we're already checking the coupon for a discount reference, or b) find a better way to chain these events together so we know that one runs after the other in a specific sequence.

torgosPizza’s picture

Priority: Critical » Major

Our problem is actually #2619448: Cart Refresh check for discount price components causes Coupon removal since checkout isn't really involved here, though I suspect the two are related.

joelpittet’s picture

Status: Needs work » Closed (fixed)

Let's assume this is still fixed;)

mishac’s picture

> Let's assume this is still fixed;)

It's definitely not fixed for me :(

joelpittet’s picture

Oh sorry to hear that @mishac. Because this is a tricky problem to solve and I'd rather not confuse two separate issues could you please write up the steps, modules, patches you are using to recreate this problem so we can solve your problem.

I would rather not muddy the waters as we have clear steps on the follow up regarding pricing rules and product pages and this issue may be a mix of unrelated info. If yours is on checkout please make a new issue.

I'll do my best to follow your steps and reproduce the problem.

torgosPizza’s picture

@mishac: We'll definitely need some steps to try and reproduce the error.

Some fixes I have found in the past for various issues:
1. Check your permissions for coupons, and check your Apache logs for any requests that might be hitting the checkout page (for example web-crawling "social sharing" plugins such as AddThis). In some cases, if an anonymous request from one of those sites hits the checkout page, what happens in Commerce is that the entire order is loaded and refreshed before it checks permissions (which to me seems strange). During that load and refresh, permissions on Coupons are also checked; so if you have Anonymous users NOT allowed to "redeem any coupon" then the coupon will get stripped away.

2. If you've recently updated Coupons and/or Discounts modules, make sure any Discount rules that are enabled and customized have been reverted to their defaults. It's possible some architectural changes have caused the Discounts to essentially become broken.

IF neither one solves it for you, we'll need to know a) what versions of the modules you're using, b) any patches applied to those modules, c) the steps to reproduce the problem if you can. Without that information we'll have no way of attempting to debug this problem.

mishac’s picture

I'm using commerce_discount 7.x-1.0-alpha7 and commerce_coupon 7.x-2.0-rc2+6-dev, and coupons with product disscounts disappear when I hit the review page of checkout.

For now I've applied the following patches, and set the coupons to apply even when the discount doesn't apply, which mostly fixes the symptom for now, but not the underlying issue.

> commerce_coupon-auto_unserialize_data-2280505-6.patch

> ommerce_coupon-apply_without_discount-2263315-25.patch

joelpittet’s picture

@mishac yes please open a new issue with exactly what you said in #20 and link it here for the 'review page'. Those patches shouldn't be necessary to reproduce the bug but mention them nontheless.

arunkumark’s picture

Hi All,

I am also facing the same problem on single page checkout. Am using Commerce Shipping and Commerce gift wrapper along with the latest version of Rules(7.x-2.10). Am tries above patch that won't helps me.

After some deep analysis, i will find that The discount is added initially to the order. But before commerce_checkout_form_validate() it will removes from the order object. This is because of discount is calculated from commerce_discount_commerce_cart_order_refresh() function. So we need to update the order total from both commerce_checkout_form_validate() and commerce_checkout_form_submit() by calling the function commerce_cart_order_refresh($order); so it will update from latest object from database.[Am not sure am correct]

4kant’s picture

I tried every patch that sounds like it was related to this issue but none of them worked for me (coupon 2.x and all related modules with the latest version).
Since this issue`s title meets the subject best I suggest to reopen it.

torgosPizza’s picture

@4kant:

I would also check the issue at #2621526: Compatibility settings other than "all" causes Discount+Coupon to be removed which seems to have a lot of overlap with this issue. If you do not use compatibility settings then you can ignore it. Otherwise - please post some steps to reproduce the issue if you can.

However I don't believe this issue actually can be reopened, so you may want to start a new one.

4kant’s picture

In my case (#23) I found that the module commerce checkout paths conflicts in some way with discount module - discounts get lost even without coupon installed.
As soon as I deactivate commerce checkout paths everything works as expected.

The issue about commerce checkout paths is here: https://www.drupal.org/node/2580951

Sorry and thanks for your support!