Always use checkout cart as main

Created on 27 May 2022, over 2 years ago
Updated 18 April 2024, 8 months ago

Problem/Motivation

Despite of all measures taken to add items checkout cart even if main cart differs from the one requested for checkout, there is still an edge case causing checkout failure.

Steps to reproduce

First, CartUnifier::getMainCarts() returns not necessarily the latest one for each bundle as $this->cartProvider->getCarts() does not always return them sorted.

Now, suppose user has 2 or more carts (due to a failure or because Commerce Combine Carts was installed not from the very beginning), say they IDs are 1 and 2. And now they log in with the 3rd card (with ID 3). Suppose $this->cartProvider->getCarts() returns the carts as 3, 2, 1, so cart 1 becomes the main one.

Now cart 3 is merged with main one (1) in CartUnifier::combineCarts(). As 3 is requested for checkout, they swap, and all the items go to 3 while 1 is deleted. On the next step cart 2 is merged with 1. However 1 is deleted so attempt to save it causes an error.

Proposed resolution

We should always serve checkout cart (if any) as a main one for given bundle instead of swapping them on later steps.

Remaining tasks

User interface changes

API changes

Data model changes

๐Ÿ› Bug report
Status

Needs review

Version

1.0

Component

Code

Created by

๐Ÿ‡จ๐Ÿ‡พCyprus alex.bukach

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

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • First commit to issue fork.
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    I think this is why a client of mine sometimes gets whitescreens in google tag from removed items and are unable to complete checkout.

    I am going to update this to an MR and do some testing.

  • Merge request !6Make checkout cart as the default cart โ†’ (Open) created by nicxvan
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan
  • Pipeline finished with Success
    8 months ago
    Total: 148s
    #150423
  • Assigned to nicxvan
  • Status changed to Needs work 8 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    I'm working on a test.

  • Pipeline finished with Failed
    8 months ago
    Total: 159s
    #150437
  • Pipeline finished with Canceled
    8 months ago
    Total: 69s
    #150444
  • Pipeline finished with Canceled
    8 months ago
    #150446
  • Pipeline finished with Failed
    8 months ago
    Total: 261s
    #150447
  • Pipeline finished with Failed
    8 months ago
    Total: 222s
    #150460
  • Pipeline finished with Failed
    8 months ago
    #150481
  • Pipeline finished with Failed
    8 months ago
    Total: 159s
    #150492
  • Pipeline finished with Failed
    8 months ago
    Total: 777s
    #150494
  • Pipeline finished with Failed
    8 months ago
    Total: 189s
    #150507
  • Issue was unassigned.
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    I'm not sure why it's getting the permission issue now. I have to pause on this for now, the actual code seems to be working.

  • Pipeline finished with Failed
    8 months ago
    Total: 164s
    #150517
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Ah I was enabling the wrong dependency.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Ok now the failure is for the test that the checkout cart is the current cart.

    So I think this means there is a bug in the code still.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Looking further, I think there might be a conflict when a user logs in during checkout flow.

    What is happening is the order assignment event fires, and the hook user login fires, or vice versa.

    One of those does a combine without a delete, the other does a combine with delete. I don't quite understand the distinction.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Note from Matt Glaman in slack:

    The module should also track the requests its running on, so it doesnโ€™t run 2x in one request like:

    /**
       * Static cache of resolved stores. One per request.
       *
       * @var \SplObjectStorage
       */
      protected $stores;
        $request = $this->requestStack->getCurrentRequest();
        if (!$this->stores->contains($request)) {
          $this->stores[$request] = $this->chainResolver->resolve();
        }
    

    if the carts were determined for the request, donโ€™t do anything

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan
  • Pipeline finished with Failed
    8 months ago
    Total: 277s
    #163615
  • Pipeline finished with Failed
    8 months ago
    Total: 206s
    #163643
  • Status changed to Needs review 8 months ago
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Not sure what is going wrong with the test. There was a long discussion in slack about being able to delete the cart when it's combined no matter what so I added that here.

    The test should be doing the following:
    Log in as admin
    Add item to cart
    Store cart id for later comparison
    Log out
    Add item to cart as anonymous
    Store anonymous cart id for later comparison
    Go to checkout
    Log in as admin
    Confirm the current cart is the anonymous cart and it is combined.
    

    This is how it works locally, but the comparison assert is failing.
    Setting to needs review for advice.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom steven jones

    steven jones โ†’ made their first commit to this issueโ€™s fork.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom steven jones

    @nicxvan So I fixed some bugs in in your test. However, you test is causing a fatal error on the test that's already in the module, and the test you're trying to add is still not working :(

    The test only pipeline run: https://git.drupalcode.org/project/commerce_combine_carts/-/jobs/2912269 fails in a bit of a weird way:

    Drupal\Core\Entity\EntityStorageException: Attempted to save order 2 with version 5. Current version is 6. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 817 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
    Drupal\Core\Entity\EntityStorageBase->doPreSave(Object) (Line: 753)
    Drupal\Core\Entity\ContentEntityStorageBase->doPreSave(Object) (Line: 483)
    Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 806)
    Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 169)
    Drupal\commerce_order\OrderStorage->save(Object) (Line: 354)
    Drupal\Core\Entity\EntityBase->save() (Line: 463)
    Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowBase->submitForm(Array, Object) (Line: 614)
    Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase->submitForm(Array, Object)
    call_user_func_array(Array, Array) (Line: 129)
    Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 67)
    Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 597)
    Drupal\Core\Form\FormBuilder->processForm('commerce_checkout_flow_multistep_default', Array, Object) (Line: 326)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 224)
    Drupal\Core\Form\FormBuilder->getForm(Object, 'login') (Line: 143)
    Drupal\commerce_checkout\Controller\CheckoutController->formPage(Object)
    call_user_func_array(Array, Array) (Line: 123)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 638)
    Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 181)
    Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 76)
    Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 37)
    Drupal\Core\Test\StackMiddleware\TestWaitTerminateMiddleware->handle(Object, 1, 1) (Line: 53)
    Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 28)
    Drupal\Core\StackMiddleware\ContentLength->handle(Object, 1, 1) (Line: 106)
    Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
    Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 36)
    Drupal\Core\StackMiddleware\AjaxPageState->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 741)
    Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

    Do you reckon that's because combine carts has just combined this cart, and saved?

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom steven jones
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    That's the root cause of an issue I've been trying to track down in commerce core!

    ๐Ÿ› OrderVersionMismatchException when updating the cart or checking out Active is where I created an issue to track this and also ๐Ÿ› Exception\OrderVersionMismatchException Closed: outdated .

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States nicxvan

    Basically if you combine the carts in the wrong order you end up on the wrong revision.

    I'll see if I can pull reproduction steps out for an issue on commerce core.

Production build 0.71.5 2024