Optimizing WooCommerce Inventory Management for Cash-App Orders

Proper inventory management is key in running an effective WooCommerce online store. It helps in balancing stock levels to meet customer needs, prevents overstocking that leads to unnecessary costs, and boosts profitability. Effective inventory management also promotes efficient operations, reduces costs, and can significantly improve a company’s profitability and cash flow.

The client did inventory the other day, and that revealed problems with Woocommerce inventory management. Here is one problem, and a solution.

A client uses Cash-App for payments, which requires manual confirmation. By default, WooCommerce does not reserve inventory for orders in the “pending” status. However, a customization in the Cash-App plugin reserves inventory even for pending orders. Unfortunately, there is no customization to release the inventory if the pending order is cancelled, leading to a significant inventory mismatch over time, with more physical inventory than reflected in the system.

Here it is again, explained in detail:

Our client accepts payments through Cash-App, where the payment method requires manual confirmation. In WooCommerce, “Pending” denotes an unconfirmed payment. No inventory reservation occurs for pending orders. For credit card orders, payment confirmation (from the payment processor) shifts the status to “Processing,” reserving inventory, or “Failed” if declined, without reserving inventory. Because an order is not marked as processing until payment is confirmed, these orders will remain with status “Pending.”

Inventory reservation only happens when the status moves from pending to processing. Conversely, upon order cancellation (status changing from “Processing” to “Cancelled”), the inventory is released.

For our client’s cash-app orders, which require manual confirmation, a customization (perhaps in a plugin?) results in inventory being reserved akin to normal orders. However, due to the non-reservation policy for pending orders, cancelling such orders won’t adjust inventory. This can create inventory mismatches.

To address this, an inventory adjustment should be triggered when the order status shifts from pending to cancelled.

This function, plk_custom_restock_on_pending_to_cancelled, has been designed to restock items when a cash-app order is cancelled. It’s meant to address a situation where orders placed via cash-app have their inventory reserved upfront, and thus, upon cancellation, this inventory needs to be replenished.

/**
 * Restock items on cancelled cash-app orders.
 *
 * @param int    $order_id   ID of the order.
 * @param string $old_status Old status of the order.
 * @param string $new_status New status of the order.
 */
function plk_custom_restock_on_pending_to_cancelled( $order_id, $old_status, $new_status ) {
	// only proceed if the order's status is changing from 'pending' to 'cancelled'.
	if ( 'pending' === $old_status && 'cancelled' === $new_status ) {
		$order = wc_get_order( $order_id );
		foreach ( $order->get_items() as $item ) {
			$product = $item->get_product(); // Get the WC_Product object.
			$qty     = $item->get_quantity(); // Get the item quantity.
			if ( $product && $product->managing_stock() ) {
				$new_qty = $product->get_stock_quantity() + $qty;
				$product->set_stock_quantity( $new_qty );
				$product->save();
			}
		}
		$order->add_order_note( __( 'Stock levels for order items have been increased due to order cancellation.', 'woocommerce' ) );
	}
}
add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 );

Here’s how the function works:

It’s an action function, meaning it’s triggered by a specific event in WooCommerce, i.e., when an order’s status changes. This is indicated by add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 ); line at the end of the code block.

The function takes three parameters: $order_id, $old_status, and $new_status, which represent the ID of the order, the old (previous) status of the order, and the new status of the order respectively.

function plk_custom_restock_on_pending_to_cancelled( $order_id, $old_status, $new_status ) {
	// Do something only when order status changes from "pending" to "cancelled".
}
add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 );

The function first checks whether the order’s status is changing from ‘pending’ to ‘cancelled’ using an if statement:

if ( 'pending' === $old_status && 'cancelled' === $new_status ) {
	// Do something if order status changes from pending to cancelled.
}

If the condition is met, the function retrieves the order details using $order = wc_get_order( $order_id );.

Then it loops through each item in the order using foreach ( $order->get_items() as $item ).

function plk_custom_restock_on_pending_to_cancelled( $order_id, $old_status, $new_status ) {
		// only proceed if the order's status is changing from 'pending' to 'cancelled'.
		if ( 'pending' === $old_status && 'cancelled' === $new_status ) {
			$order = wc_get_order( $order_id );
			foreach ( $order->get_items() as $item ) {
				// Do something with the order item.
			}
		}
	}
	add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 );

For each item, it retrieves the associated product and the quantity of the product ordered using $product = $item->get_product(); and $qty = $item->get_quantity();.

If the product is managing stock (if ( $product && $product->managing_stock() )), it updates the stock quantity of the product by adding the quantity of the item in the order back to the stock. This is achieved by $new_qty = $product->get_stock_quantity() + $qty; and $product->set_stock_quantity( $new_qty );.

After updating the stock quantity, it saves the changes to the product object using $product->save();.

function plk_custom_restock_on_pending_to_cancelled( $order_id, $old_status, $new_status ) {
	// only proceed if the order's status is changing from 'pending' to 'cancelled'.
	if ( 'pending' === $old_status && 'cancelled' === $new_status ) {
		$order = wc_get_order( $order_id );
		foreach ( $order->get_items() as $item ) {
			$product = $item->get_product(); // Get the WC_Product object.
			$qty     = $item->get_quantity(); // Get the item quantity.
			if ( $product && $product->managing_stock() ) {
				$new_qty = $product->get_stock_quantity() + $qty;
				$product->set_stock_quantity( $new_qty );
				$product->save();
			}
		}
	}
}
add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 );

Lastly, it adds a note to the order indicating that the stock levels have been increased due to the order cancellation ($order->add_order_note( __( 'Stock levels for order items have been increased due to order cancellation.', 'woocommerce' ) );).

Here, again, is the finished function, along with the function comments. Place the function on functions.php or any other file that might contain WordPress or WooCommerce functions.

/**
 * Restock items on cancelled cash-app orders.
 *
 * @param int    $order_id   ID of the order.
 * @param string $old_status Old status of the order.
 * @param string $new_status New status of the order.
 */
function plk_custom_restock_on_pending_to_cancelled( $order_id, $old_status, $new_status ) {
	// only proceed if the order's status is changing from 'pending' to 'cancelled'.
	if ( 'pending' === $old_status && 'cancelled' === $new_status ) {
		$order = wc_get_order( $order_id );
		foreach ( $order->get_items() as $item ) {
			$product = $item->get_product(); // Get the WC_Product object.
			$qty     = $item->get_quantity(); // Get the item quantity.
			if ( $product && $product->managing_stock() ) {
				$new_qty = $product->get_stock_quantity() + $qty;
				$product->set_stock_quantity( $new_qty );
				$product->save();
			}
		}
		$order->add_order_note( __( 'Stock levels for order items have been increased due to order cancellation.', 'woocommerce' ) );
	}
}
add_action( 'woocommerce_order_status_changed', 'plk_custom_restock_on_pending_to_cancelled', 10, 3 );

Image by TigerPak from Pixabay

Leave a Reply

Your email address will not be published. Required fields are marked *