Cart items deleted without invoking the cart manager do not delete referenced registrations

Created on 2 March 2024, 12 months ago
Updated 20 March 2024, 11 months ago

Problem/Motivation

Cart items deleted programmatically without invoking the cart manager can leave detached registrations behind. These registrations should be deleted just like they would be if the items were removed using the cart manager API.

Steps to reproduce

  1. Install module commerce_cart_redirection.
  2. Make sure that the checkbox "Clear cart before add" is checked at /admin/commerce/config/commerce_cart_redirection.
  3. Create 2 products for users to register.
  4. User registers for the first product without making any payment.
  5. The same user clicks the Register button for the second product. At this time, the first item is removed from the cart and the second item is added. However, the registration from the first item still exists and is now detached from any item.
πŸ› Bug report
Status

Fixed

Version

3.1

Component

Code

Created by

πŸ‡¨πŸ‡¦Canada franceslui

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @franceslui
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    Thanks for the post @franceslui. I tried this on a test system and it works correctly. Also this is a common scenario, for which nobody else has reported a problem. So I suspect that the issue is related to your installation and not a general problem. To help get to the bottom of this, would you be able to provide more information:

    * Is either product on a wait list? If you have commerce_registration_waitlist installed, there is a scenario where a registration can move to a different commerce order item on a different order if a registration was on a waiting list and then more room becomes available.
    * Does the registration itself still exist? If you go to the Manage Registrations tab for the first product, is the registration still there.
    * Is this something that used to work and now no longer does?
    * Do you have any custom code that moves registrations between orders, or deletes registrations from carts?
    * Does it matter which products. Or does the problem occur with any two products in your store.
    * Is it possible the first registration expired - or does the problem occur even when little time has passed between adding the items to the cart.

    Let's try to isolate the issue and go from there. For now, I am going to mark this as a support request. We can change it back to a Bug if it turns out this is a general problem with the module.
    John

  • πŸ‡¨πŸ‡¦Canada franceslui

    @john.oltman Thank you very much for your quick reply and asking questions to help resolve the issue.
    I have found out what caused the issue. So, I have modified the section "Steps to reproduce" to explain how the issue can be reproduced.

    To get around the issue, I added the following code

    function hook_entity_delete(EntityInterface $entity) {
      if ($entity instanceof OrderItemInterface) {
        // When an order item is deleted, change status of all existing registrations in the cart to 'canceled'.
        // An order item can be deleted by the module commerce_cart_redirection because we have checked the checkbox
        // "Clear cart before add" with help text "Select whether everything that is currently in the cart will be
        // removed before adding a new item" at /admin/commerce/config/commerce_cart_redirection.
        $storage = \Drupal::entityTypeManager()->getStorage('registration');
        $registrations = $storage->loadByProperties([
          'order_id' => $entity->getOrderId(),
        ]);
        if (!empty($registrations)) {
          // The following code is originally from OrderSubscriber's function onOrderUpdate(OrderEvent $event)
          foreach ($registrations as $registration) {
            if ($registration->isActive() || $registration->isHeld()) {
              if (!$registration->isComplete()) {
                if ($workflow = $registration->getWorkflow()->getTypePlugin()) {
                  if ($workflow->hasState('canceled')) {
                    $registration->set('state', 'canceled');
                    $registration->save();
                  }
                }
              }
            }
          }
        }
      }
    }
    

    Do you think you could do something similar in your module? Thank you again for your help.

  • πŸ‡ΊπŸ‡ΈUnited States john.oltman
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman
  • Status changed to Fixed 12 months ago
  • πŸ‡ΊπŸ‡ΈUnited States john.oltman

    Thanks @franceslui, this did turn out to be a bug in the module. Once this is released, you can remove the custom code you added. The registrations for the deleted cart item will be deleted instead of canceled. For now, the fix is committed to the dev branch.

  • πŸ‡¨πŸ‡¦Canada franceslui

    @john.oltman. Thank you very much for your quick reply and fix. I greatly appreciate your help!

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

  • πŸ‡¨πŸ‡­Switzerland tcrawford

    Hi. This fix works fine if an order item is deleted and the cart still exists. The fix does not work in a common use case where the order item is deleted in the postDelete of the order. This happens commonly when an abandoned cart is deleted via CRON. The issue is that the code tries to load the order to check if it is a cart (but can't as it was deleted) and therefore does not clean up the registration. Shall I re-open this issue or create a separate issue?

      // Delete registrations referenced by an order item being deleted from
      // a cart, unless the registration is already complete. There is a cart
      // subscriber that handles cart item removals, but modules can delete
      // items from a cart programmatically without invoking the cart manager.
      // @see \Drupal\commerce_registration\EventSubscriber\CartEventSubscriber
      elseif ($entity instanceof OrderItemInterface) {
        if ($order = $entity->getOrder()) {
          $is_cart = FALSE;
          if (!$order->get('cart')->isEmpty()) {
            $is_cart = $order->get('cart')->getValue()[0]['value'];
          }
          if ($is_cart && !$entity->get('registration')->isEmpty()) {
            $registrations = $entity->get('registration')->referencedEntities();
            foreach ($registrations as $registration) {
              if (!$registration->isComplete()) {
                $registration->delete();
              }
            }
          }
        }
      }
Production build 0.71.5 2024