magento2: M2.3.1: Unable to place order for bundle when child quantity > 1

When placing an order for a bundle product with child product quantity set to a value greater than 1, the following error message is thrown: We found an invalid quantity to invoice item "%1".

This happens for any payment method where invoices are automatically created during order creation e.g.

  • PayPal Express Checkout + Payment Action = Sale
  • Zero Subtotal Checkout + Automatically Invoice All Items = Yes

Preconditions (*)

  1. Magento 2.3.1

  2. Test bundle product: – General configuration Screenshot 2019-05-06 at 12 10 39 PMChild product (ensure Ship Bundle Items is set to “Together”) Screenshot 2019-05-05 at 2 31 25 PM

  3. Admin > Stores > Configuration > Sales > Payment Methods configuration: – PayPal Express Checkout (note: a sandbox account will work as well) Enable this solution: Yes Payment Action: SaleZero Subtotal Checkout Enabled: Yes New Order Status: Processing Automatically Invoice All Items: Yes

  4. Admin > Stores > Configuration > Sales > Shipping Methods configuration: – Free Shipping Enabled: Yes


Steps to reproduce (as customer) (*)

  1. Add Test bundle to the shopping cart, making sure to set the child quantity to a value > 1: Screenshot 2019-05-06 at 12 26 32 PM
  2. Proceed to Checkout
  3. Select PayPal Express Checkout as the payment method
  4. Click Continue to PayPal
  5. Make payment on PayPal

Expected result (*)

  1. Customer is redirected back to Magento’s Order Success page Screenshot 2019-05-06 at 12 39 03 PM

Actual result (*)

  1. Error message We found an invalid quantity to invoice item "%1". is displayed: Screenshot 2019-05-06 at 12 41 58 PM

Steps to reproduce (as admin) (*)

  1. Create a new order from Admin > Sales > Orders
  2. Add the Test bundle to the order, making sure the child quantity is set to a value greater than 1: Screenshot 2019-05-06 at 12 46 11 PM
  3. Set a custom price of 0 for the Test bundle Screenshot 2019-05-06 at 12 47 42 PM
  4. Under Shipping Method, select Free Shipping
  5. Payment Method section should now display “No Payment Information Required”, and the order total should be 0: Screenshot 2019-05-06 at 12 56 14 PM
  6. Click Submit Order

Expected result (*)

  1. New zero subtotal order is successfully created
  2. Invoice is automatically created for this zero subtotal order

Actual result (*)

  1. Error message We found an invalid quantity to invoice item "Test". is displayed: Screenshot 2019-05-06 at 1 06 53 PM

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 20 (6 by maintainers)

Most upvoted comments

This bug was introduced in commit 2fb6b3b81ada22e1a172272a7eb5454a50a71a2d in the following 2 private methods from app/code/Magento/Sales/Model/Service/InvoiceService.php:

    /**
     * Prepare qty to invoice for parent and child products if theirs qty is not specified in initial request.
     *
     * @param Order $order
     * @param array $qtys
     * @return array
     */
    private function prepareItemsQty(Order $order, array $qtys = [])
    {
        foreach ($order->getAllItems() as $orderItem) {
            if (empty($qtys[$orderItem->getId()])) {
                if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { // herein lies the problem -- order item id is assumed to be > 0
                    $qtys[$orderItem->getId()] = $orderItem->getQtyOrdered() - $orderItem->getQtyInvoiced();
                } else {
                    continue;
                }
            }
            $this->prepareItemQty($orderItem, $qtys);
        }
        return $qtys;
    }

    /**
     * Prepare qty to invoice for bundle products
     *
     * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
     * @param array $qtys
     */
    private function prepareBundleQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
    {
        if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { // herein lies the problem -- order item id is assumed to be > 0
            foreach ($orderItem->getChildrenItems() as $childItem) {
                $bundleSelectionAttributes = $this->serializer->unserialize(
                    $childItem->getProductOptionByCode('bundle_selection_attributes')
                );
                $qtys[$childItem->getId()] = $qtys[$orderItem->getId()] * $bundleSelectionAttributes['qty'];
            }
        }
    }

The bug is triggered when the private method prepareItemsQty() is called by the public method prepareInvoice().

ALL existing test cases which call the prepareInvoice() method assume an order has already been successfully created (and hence, all order items belonging to the order have an item id > 0). This assumption is invalid when invoices are created together with orders, as is the case with

  • PayPal Express Checkout + payment action = “Sale”
  • Zero Subtotal Checkout + automatically invoice all items = “Yes”

To resolve this, the 2 private methods above should also check for a non-zero bundle item id:

    /**
     * Prepare qty to invoice for parent and child products if theirs qty is not specified in initial request.
     *
     * @param Order $order
     * @param array $qtys
     * @return array
     */
    private function prepareItemsQty(Order $order, array $qtys = [])
    {
        foreach ($order->getAllItems() as $orderItem) {
            if (empty($qtys[$orderItem->getId()])) {
                if ($orderItem->getId() && $orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { // bugfix
                    $qtys[$orderItem->getId()] = $orderItem->getQtyOrdered() - $orderItem->getQtyInvoiced();
                } else {
                    continue;
                }
            }
            $this->prepareItemQty($orderItem, $qtys);
        }
        return $qtys;
    }

    /**
     * Prepare qty to invoice for bundle products
     *
     * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
     * @param array $qtys
     */
    private function prepareBundleQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
    {
        if ($orderItem->getId() && $orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { // bugfix
            foreach ($orderItem->getChildrenItems() as $childItem) {
                $bundleSelectionAttributes = $this->serializer->unserialize(
                    $childItem->getProductOptionByCode('bundle_selection_attributes')
                );
                $qtys[$childItem->getId()] = $qtys[$orderItem->getId()] * $bundleSelectionAttributes['qty'];
            }
        }
    }

@mystix I have added your fix and it seems working now 😃 . thanks for the fix mate. you are a lifesaver.

@craigcarnell Try @mystix fix. it worked for me.

@mystix just wanted to say thank you for the detailed report and suggested fix - we’ve hit this exact issue on a live site (recently upgraded to 2.3.1) with Stripe also.