magento2: Unable to execute addSimpleProduct mutation while other items out of stock

Summary (*)

We are using Magento as a headless backend for our front-facing application, we are using the GraphQL API to build the frontend.

We noticed if we added a product to cart, and that product later became out of stock, any further attempts to add items to cart will fail with Some cart items are out of stock effectively locking up the cart from adding other items or changing other items quantities.

This is the returned GraphQL error:

"message": "Some of the products are out of stock.",

This is a breaking issue for us, as the alternative would result in a very poor experience for our users.

Currently, we have things set up that the frontend is able to detect whenever this exception is thrown and tries to amend it by removing the offending items and attempting to re-play whatever mutations are done to the cart since then.

Needless to say, this is a very flaky solution and it doesn’t make sense to have this restriction in the first place since the REST API seems to be fine with this scenario.

Examples (*)

To Reproduce:

  1. Using the GraphQL API: Add an item to cart.
  2. Mark it as out-of-stock from the admin dashboard.
  3. Using the GraphQL API: Try to add other products to cart.

I have successfully reproduced this on a fresh magento environment

Proposed solution

Ideally, the GraphQL API should allow mutating the cart as long as the out-of-stock item is not being the target of the mutation (updating its quantity for example).

A slightly less robust solution is to allow the mutation to go through, but return errors alongside the data prompting the GQL client to update their UI and also be aware of possible problems.

GraphQL requests for reproducing

Create an empty cart

mutation {
  createEmptyCart
}

Add product to cart

mutation {
  addSimpleProductsToCart(
    input: {
      cart_id: "{ CART_ID }"   # <--- cart id from the previous request goes here
      cart_items: [
        {
          data: {
            quantity: 1
            sku: "simple-product"   # <--- product sku goes here
          }
        }
      ]
    }
  ) {
    cart {
      items {
        id
        product {
          sku
          stock_status
        }
        quantity
      }
    }
  }

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

Overcome this issue by overriding Cart model adding line below to remove error during process.

<?php
declare(strict_types=1);

namespace Robusta\CartStockErrorHandler\Model\Cart;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\Message\AbstractMessage;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\Quote;
use Magento\QuoteGraphQl\Model\Cart\AddProductsToCart as MAddProductsToCart;
use Magento\QuoteGraphQl\Model\Cart\AddSimpleProductToCart;

class AddProductsToCart extends MAddProductsToCart
{
    /**
     * @var CartRepositoryInterface
     */
    private $cartRepository;

    /**
     * @var AddSimpleProductToCart
     */
    private $addProductToCart;

    /**
     * @param CartRepositoryInterface $cartRepository
     * @param AddSimpleProductToCart $addProductToCart
     */
    public function __construct(CartRepositoryInterface $cartRepository, AddSimpleProductToCart $addProductToCart)
    {
        $this->cartRepository = $cartRepository;
        $this->addProductToCart = $addProductToCart;
        parent::__construct($cartRepository, $addProductToCart);
    }

    /**
     * Add products to cart
     *
     * @param Quote $cart
     * @param array $cartItems
     * @throws GraphQlInputException
     * @throws LocalizedException
     * @throws GraphQlNoSuchEntityException
     */
    public function execute(Quote $cart, array $cartItems): void
    {
        foreach ($cartItems as $cartItemData) {
            $this->addProductToCart->execute($cart, $cartItemData);
        }

        // This line must be added to remove thrown error
        $cart->removeErrorInfosByParams('stock', []);

        if ($cart->getData('has_error')) {
            throw new GraphQlInputException(
                __('Shopping cart error: %message', ['message' => $this->getCartErrors($cart)])
            );
        }

        $this->cartRepository->save($cart);
    }

    /**
     * Collecting cart errors
     *
     * @param Quote $cart
     * @return string
     */
    private function getCartErrors(Quote $cart): string
    {
        $errorMessages = [];

        /** @var AbstractMessage $error */
        foreach ($cart->getErrors() as $error) {
            $errorMessages[] = $error->getText();
        }

        return implode(PHP_EOL, $errorMessages);
    }
}