WooCommerce: How to Stop Spam Orders on Free Products

I’ve been hit once again by a bot attack.

This time, I woke up to dozens of fake orders for my best-selling free mini plugin: the WooCommerce Autocomplete Orders Mini-Plugin. All of them placed overnight, all using obviously fake email addresses, and all totally useless.

Besides clogging up my order list and analytics, these spam orders also triggered multiple admin emails and slowed down my workflow.

Free products are a great way to provide value and attract users — but they also open the door to abuse.

That’s why I decided to implement a simple solution: limit how many times each free product can be ordered per day. Once the daily limit is hit, the product becomes temporarily unavailable with a “come back tomorrow” message.

In this post (and the video below), I’ll show you exactly how I did it, with a custom snippet that targets all free products in a specific category.

Spam WooCommerce Orders

It all started here, this morning, with dozens of “Completed” order emails. I immediately knew that wasn’t normal. So many orders, in a short amount of time, all targeting the same free product, and from fake email addresses:

Existing Business Bloomer Snippet

I then remembered I shared a snippet a couple of years ago, to limit the daily sales of a given product ID:

Now, what if I tweaked that to target only free products, and only a given product category (as I know spam orders target only those)?

ChatGPT and a Couple of Prompts Later…

So I asked ChatGPT:

I have this code. Make it work for ALL free products in the "plugins" product category, and display a message to come back tomorrow once the item is available again: [pasted my snippet]

Returned code was decent enough and well written, but wasn’t for some reason doing what I asked; when looping through today’s orders, I wanted to count the number of times a GIVEN product was purchased. So I refined the code:

There is no check inside the foreach to see if it's the given product_id

ChatGPT was nice enough: “You’re absolutely right — my previous version counts all free products in the “plugins” category instead of counting per individual product. Here’s the corrected version, which tracks sales per product ID within the free products of the “plugins” category”.

Finally, I refined the code with the “.woocommerce-error” class and UL-LI structure to format the error message that displays on free products once they’ve gone over the daily limit.

Now the code was perfect (find it below).

My free product, now that I received so many spam orders, is now not purchasable again for today:

PHP Snippet: Limit Daily Orders for Free Products in WooCommerce

This PHP snippet helps WooCommerce store owners prevent spam and abuse on free products by limiting how many times a product can be ordered each day. Specifically, it targets free products within the “plugins” category and makes them temporarily unavailable after 5 daily orders.

Once the limit is reached, the product becomes non-purchasable and displays an error message asking users to come back tomorrow.

This is a lightweight, code-only solution that doesn’t require additional plugins and is ideal for stores offering free downloads that frequently attract bots or fake orders. Customize the category or limit as needed.

/**
 * @snippet       Limit Spam WooCommerce Orders (Free Products)
 * @tutorial      https://businessbloomer.com/woocommerce-customization
 * @author        Rodolfo Melogli, Business Bloomer
 * @compatible    WooCommerce 9
 * @community     https://businessbloomer.com/club/
 */

add_filter( 'woocommerce_is_purchasable', 'bbloomer_limit_daily_sales_free_plugins', 10000, 2 );

function bbloomer_limit_daily_sales_free_plugins( $is_purchasable, $product ) {

    // Only target free products in the "plugins" category
    if ( floatval( $product->get_price() ) > 0 ) return $is_purchasable;
    if ( ! has_term( 'plugins', 'product_cat', $product->get_id() ) ) return $is_purchasable;

    $product_id = $product->get_id();
    $today = date( 'Y-m-d' );
    $count = 0;

    $orders = wc_get_orders([
        'limit'        => -1,
        'date_created' => $today,
        'status'       => [ 'processing', 'completed' ],
        'return'       => 'ids',
    ]);

    foreach ( $orders as $order_id ) {
        $order = wc_get_order( $order_id );
        foreach ( $order->get_items() as $item ) {
            $item_product_id = $item->get_product_id();
            if ( $item_product_id && $item_product_id === $product_id ) {
                $count += absint( $item->get_quantity() );
            }
        }
    }

    // Limit reached: make it not purchasable
    if ( $count >= 5 ) {
        add_filter( 'woocommerce_get_price_html', 'bbloomer_free_plugin_sold_out_message', 9999, 2 );
        return false;
    }

    return $is_purchasable;
}

function bbloomer_free_plugin_sold_out_message( $price_html, $product ) {
    if ( floatval( $product->get_price() ) == 0 && has_term( 'plugins', 'product_cat', $product->get_id() ) ) {
        return '<ul class="woocommerce-error"><li>This free plugin is out of stock for today. Please come back tomorrow!</li></ul>';
    }
    return $price_html;
}

Video Walkthrough

Where to add custom code?

You should place custom PHP in functions.php and custom CSS in style.css of your child theme: where to place WooCommerce customization?

This code still works, unless you report otherwise. To exclude conflicts, temporarily switch to the Storefront theme, disable all plugins except WooCommerce, and test the snippet again: WooCommerce troubleshooting 101

Related content

Rodolfo Melogli

Business Bloomer Founder

Author, WooCommerce expert and WordCamp speaker, Rodolfo has worked as an independent WooCommerce freelancer since 2011. His goal is to help entrepreneurs and developers overcome their WooCommerce nightmares. Rodolfo loves travelling, chasing tennis & soccer balls and, of course, wood fired oven pizza. Follow @rmelogli

Questions? Feedback? Customization? Leave your comment now!
_____

If you are writing code, please wrap it like so: [php]code_here[/php]. Failure to complying with this, as well as going off topic or not using the English language will result in comment disapproval. You should expect a reply in about 2 weeks - this is a popular blog but I need to get paid work done first. Please consider joining the Business Bloomer Club to get quick WooCommerce support. Thank you!

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