After more than half a year silence, this is now my third blog post within two days :)

This time I'm showing you, how you can automatically add another order item after a product has been added to the cart in Drupal Commerce 2.x. The most important info here is, how you can pass the pitfall of possible transient data problems here.

Prologue

Our use case was that our customer wanted to optionally sell warranty extensions with their products. The plan was to extend the add to cart form on the product page with a checkbox, which allows to add the warranty extension. Because this counts as an insurance and insurances are taxed especially in Austria and therefore VAT free, simply adding the amount to the product's order item was not a valid choice. We had to add extra order items for that. That would still have been possible to solve within a custom form submit callback. However, we prefer OOP based approaches, and so we just stored the info of checking the selection temporarliy and decided to react on this in an event subscriber later on. Commerce offers the CartEvents::CART_ENTITY_ADD event to react on the add to cart event.

Implement the event subscriber

I won't describe the basics of how to write an event subscriber in general here. The event we'll want to subscribe to is the \Drupal\commerce_cart\Event\CartEvents::CART_Entity_ADD and you'll receive an event of type \Drupal\commerce_cart\Event\CartEntityAddEvent. Let's assume we register a callback named addWarrantyExtension(). This would look like that:


  /**
   * Adds an warranty extension order item on add to cart event, if requested.
   *
   * @param \Drupal\commerce_cart\Event\CartEntityAddEvent $event
   *   The add to cart event.
   */
  public function addWarrantyExtension(CartEntityAddEvent $event) {
    $add_warranty_extension = doAnyCustomCheckWeNeedHere();
    if (!$add_warranty_extension) {
      return;
    }

    $my_custom_purchasable_entity = createTheWarrantyExtensionOrWhatever();

    // Don't do it like that. This can cause problems - explained below in the post!
    //$cart = $order_item->getOrder();
    // This is the right way!
    $cart = $event->getCart();

    $my_order_item = $this->orderItemStorage->createFromPurchasableEntity($my_custom_purchasable_entity );
    $my_order_item->set('order_id', $cart->id());
    $my_order_item->save();
    $cart->addItem($my_order_item);
  }

This is a shortened outline about we have done. But the most interesting thing is already mentioned in the code's comments above:

Beware of transient data problems

The original version of our code didn't fetched the cart by using $event->getCart() - I've simply overseen that this getter exists, neverminded and used $order_item->getOrder() instead. At first glance, everything seems to work nicely, but literally the last tests before going live with this features showed us some problems.

What happened? Upon adding a product with checking the warranty extension, the cart showed the product only. On a closer look, I saw the order item with the warranty extension existed, but the order's order items field only contained the product. A few tries later I saw that this issue can be reproduced every time the order entity was created before within the same request. If the cart existed before the requested, no problem occured. I've took another close look and saw that the order within our event subscriber already had an ID though, but no order items were set at all - so the originally added product was missing. Upon save at last, the product was in the cart, but our warranty item was missing.

However, as soon as I have started to correctly use the getCart() function of the event to fetch the order entity, I was no more having any problem. The product was present within the event subscriber, and the additionally added warranty extension was still part of the cart upon visiting the cart page :)

Kommentare1

Klartext

  • Keine HTML-Tags erlaubt.
  • Zeilenumbrüche und Absätze werden automatisch erzeugt.
  • Website- und E-Mail-Adressen werden automatisch in Links umgewandelt.
Der Inhalt dieses Feldes wird nicht öffentlich zugänglich angezeigt.

Jan

vor 1 year

Thank you so much for this
Thank you so much for this blog post. I was hitting the $order_item->getOrder() vs $event->getCart() problem in my Cart Events subscriber. Data was mysteriously getting lost along with obscure OrderVersionMismatch errors. Using $event->getCart() solved the problem. This saved me hours.