I have free downloadable products on my Magento 2 shop (Magento 2.3.5 p1).

If a customer purchases a free downloadable product, he gets a zero subtotal order which I set Zero Subtotal Checkout to status processing via Magento Payment settings automatically.

This closes the order automatically and does not allow the customer to download his product. If I set the status to 'Pending'... then I have to generate a manual invoice to enable customer to download his order.

This behaviour was not observed in Magento 1, there it was always ending with status "complete" and the download was enabled automatically.

Means I get customers complaining because they can't download their zero total orders.

A zero total order should allow status to invoiced automatically. Fix required for this problem.

  • You can do this with littel customization into this and done that one. Aug 18, 2020 at 5:42

1 Answer 1


This seems to be a known bug on magento 2.3.x See here: https://github.com/magento/magento2/issues/25177

I m just adding the solution here but it is just from this ticket:

Reference Class: Magento\Sales\Model\ResourceModel\Order\Handler\State

<?xml version="1.0" encoding="UTF-8"?>
    <preference for="Magento\Sales\Model\ResourceModel\Order\Handler\State" type="Vendor\VirtualOrderStatus\Sales\Model\ResourceModel\Order\Handler\State" />

public function check(Order $order)
        $currentState = $order->getState();
        if ($currentState == Order::STATE_NEW && $order->getIsInProcess()) {
            $currentState = Order::STATE_PROCESSING;

        if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice()) {
            if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE])
                && !$order->canCreditmemo()
                && !$order->canShip()
                && ($order->getGrandTotal() > 0 || $this->isFreePaymentFullyRefunded($order)) // check if it is free payment and also all refunded
            ) {

            } elseif ($currentState === Order::STATE_PROCESSING && !$order->canShip()) {
        return $this;

    private function isFreePaymentFullyRefunded(Order $order)
        $totalInvoicedItem = 0;
        $totalRefundItem = 0;

        foreach ($order->getItems() as $item) {
            $totalInvoicedItem += $item->getQtyInvoiced();
            $totalRefundItem  += $item->getQtyRefunded();

        return $totalRefundItem > 0 && $totalRefundItem == $totalInvoicedItem;


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.