Skip to content
🛒 Duplicate orders in WooCommerce: 3 scripts for blocking at the cart level

🛒 Duplicate orders in WooCommerce: 3 scripts for blocking at the cart level

The customer clicked "Place order" twice, and now there are two identical orders in the admin panel just a couple of seconds apart. The money was charged twice, the customer is furious, and you're dealing with a refund.

Duplicate orders are not a hypothetical situation. Roughly one in three stores on WooCommerce encounters them, especially with unstable hosting or slow payment gateways. But even on a fast server, a user can re-add a product via URL or buy a free digital product dozens of times.

We've put together three working scripts that cover different duplication scenarios: from forcing single-unit sales to blocking repeated URL additions. Below is a complete breakdown with code.

Why orders get duplicated in WooCommerce

💡 Quick overview:

  • You'll learn why orders get duplicated at the cart, URL, and repeat-purchase level
  • You'll install a script to force single-unit sales via meta fields
  • You'll prevent repeat purchases of free variations (digital goods, coupons)
  • You'll eliminate duplication via URL with add-to-cart on page refresh
  • You'll test the protection and learn what else can cause duplicates at the server level

Order duplicates arise from three main scenarios.

First, the user clicks "Place order" several times. This happens when the page lags and the person sees no response and clicks the button again. On slow hosting, a regular story.

Second scenario, free products. If you have a free digital product, nothing stops one user from "buying" it ten times in a row. WooCommerce out of the box does not track whether this customer has taken the same free product before.

Third, a direct link with the ?add-to-cart=123 parameter. Refreshed the cart page, the product got added again. Followed a link from a newsletter twice, two copies in the cart.

All three scenarios are solved server-side via WooCommerce hooks. No plugins, just code.

Method 1: Forcing single-unit sales via meta fields

The first script forces WooCommerce to treat a specific product as "sold individually," even if that checkbox is not ticked in the product settings. Control is via a meta field.

When the meta field maximum_allowed_quantity equals 1, the woocommerce_is_sold_individually filter returns true, and WooCommerce itself blocks adding a second unit to the cart. Works for both simple products and variations.

Add the code to your child theme's functions.php or via the Code Snippets plugin:

1function sd_woocommerce_is_sold_individually( $return, $product ) {
2 switch ( $product->get_type() ) {
3 case 'variation':
4 if ( $product->get_meta( 'variation_maximum_allowed_quantity' ) == 1 ) {
5 return true;
6 }
7 break;
8 case 'simple':
9 if ( $product->get_meta( 'maximum_allowed_quantity' ) == 1 ) {
10 return true;
11 }
12 break;
13 }
14 return $return;
15}
16add_filter( 'woocommerce_is_sold_individually', 'sd_woocommerce_is_sold_individually', 10, 2 );

What happens here: the woocommerce_is_sold_individually hook fires every time WooCommerce checks whether a product can be added to the cart more than once. The function looks into the product's meta fields and, if it sees maximum_allowed_quantity = 1, forcibly enables "Sold Individually" mode. For variations, the field is called variation_maximum_allowed_quantity.

How to apply. Go into editing any product or variation, add a custom field with the name maximum_allowed_quantity (or variation_maximum_allowed_quantity for a variation) and the value 1. Save. That's it, now this product cannot be put into the cart twice.

Method 2: Preventing repeat purchases of free variations

Free digital products are the main gap in duplicate protection. The user is logged in, the product price is zero, WooCommerce lets the order through without question. Ten such orders from one customer, ten identical entries in the admin panel.

The script below checks via wc_customer_bought_product() whether this user has bought the same product before. If yes, adding to cart is blocked with a clear notice.

1function sd_disable_repeat_purchase_for_free_products( $passed, $product_id, $quantity, $variation_id = 0 ) {
2 $current_user = wp_get_current_user();
3 $product = $variation_id > 0 ? wc_get_product( $variation_id ) : wc_get_product( $product_id );
4 $target_id = $variation_id > 0 ? $variation_id : $product_id;
5
6 if ( floatval( $product->get_price() ) == 0 ) {
7 if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $target_id ) ) {
8 wc_add_notice(
9 sprintf( __( 'You have already purchased "%s" and cannot purchase it again.', 'sd' ), $product->get_name() ),
10 'error'
11 );
12 return false;
13 }
14 }
15 return $passed;
16}
17add_filter( 'woocommerce_add_to_cart_validation', 'sd_disable_repeat_purchase_for_free_products', 10, 4 );

After installation, the user sees an error message when trying to add an already-purchased free product to the cart. The order itself is not created.

Two important conditions. First, the customer must be logged in. For guests, wp_get_current_user() returns an empty object, and the check will not work. Second, the product price must be exactly zero (floatval eliminates formatting issues). The script will not touch products with a price above zero.

Method 3: Protection against duplication via URL with add-to-cart

WooCommerce allows adding a product to the cart via a direct link: /cart/?add-to-cart=123. Convenient for marketing, but if the user refreshes the page or follows the link again, the product gets added again.

This script intercepts the cart or checkout page load and, if the product from the URL parameter is already in the cart, silently removes the query parameters and redirects to a clean URL.

1function sd_prevent_duplicate_add_to_cart_redirect() {
2 if ( ( is_cart() || is_checkout() ) && isset( $_GET['add-to-cart'] ) ) {
3 $current_url = home_url( add_query_arg( null, null ) );
4 $product_id = intval( $_GET['add-to-cart'] );
5 $variation_id = isset( $_GET['variation_id'] ) ? intval( $_GET['variation_id'] ) : 0;
6
7 foreach ( WC()->cart->get_cart() as $cart_item ) {
8 if ( $cart_item['product_id'] == $product_id
9 && ( $variation_id == 0 || $cart_item['variation_id'] == $variation_id ) ) {
10 wp_redirect( remove_query_arg( array( 'add-to-cart', 'variation_id' ), $current_url ) );
11 exit;
12 }
13 }
14 }
15}
16add_action( 'template_redirect', 'sd_prevent_duplicate_add_to_cart_redirect' );

The template_redirect hook fires before WordPress starts rendering the page. The script checks if the add-to-cart parameter is in the URL, looks for that product in the cart contents (WC()->cart->get_cart()), and if found, does a wp_redirect to the same page without query parameters.

Repeated addition via URL is eliminated. At the same time, the first addition (when the product is not yet in the cart) works as usual.

Putting it all together

All three scripts do not conflict and work independently:

  • The first limits the quantity of a single product to 1, protection against multiple additions.
  • The second blocks repeat purchases of free products, protection against "click-spamming" freebies.
  • The third removes duplication via URL, protection against accidental repeat additions.

Add the code via the Code Snippets on WP.org plugin, not directly into the theme's functions.php. The plugin lets you enable and disable snippets one by one without breaking the site on error, and does not lose code when switching themes. Create three separate snippets, it's easier to debug that way.

What else to check if duplicates persist

The scripts above cover duplicates at the cart and checkout level. But there are two more sources of the problem that code cannot fix.

Caching of cart and checkout pages. Caching plugins like WP Rocket or LiteSpeed Cache exclude these pages by default, but after a settings update the exclusion can reset. Check: cart, checkout, and my-account must not be cached at the plugin level, the server level, or in the CDN.

Retries from the payment gateway. If the gateway (Stripe, PayPal) does not receive confirmation from the server in time, it sends the notification again, and WooCommerce creates a second order. The cause is usually slow hosting or a firewall blocking callback requests. Solution: check the gateway logs for repeated webhook sends and make sure the callback URL is accessible from the outside.

How to test the protection

After installing the scripts, be sure to test each scenario on a test site.

Method 1, create a product with the meta field maximum_allowed_quantity = 1 and try to add two units to the cart. Only one should be added.

Method 2, log in, buy a free product, then try to buy it again. You should see an error message.

Method 3, add a product to the cart via a direct link with ?add-to-cart=ID, then refresh the page. The number of items in the cart should not change.

Test in a separate installation or on a staging site. On a live store, only after a full check.

⁉️🤔 Frequent questions

Why do orders get duplicated if the "Place order" button was clicked once?

The reason is almost always a slow server response. The user clicks the button, the request goes out, but the page does not refresh for several seconds. The person thinks they didn't click and clicks again. Both requests reach WooCommerce, and two orders are created. The scripts in this article do not catch this situation; here, client-side caching (disable button after click) or server-side order uniqueness check via a nonce token helps.

Can a payment gateway create duplicates on its own?

Yes. If the gateway does not receive an HTTP 200 from your server in response to an IPN/webhook, it repeats the send. On each retry, WooCommerce can create a new order. The problem is solved by speeding up hosting and checking that the gateway's callback URL is not blocked by a firewall and does not require HTTP authentication.

Does this protection block genuine repeat orders from a customer?

No. The scripts work at the add-to-cart stage, not at the order creation stage. If a customer deliberately placed one order, and a week later a second one, the scripts will not interfere. Exception: method 2, which prohibits a repeat purchase of a free product by the same user.

Is it mandatory to use Code Snippets, or can it go in functions.php?

You can use functions.php, but we recommend Code Snippets. With a typo in functions.php code you get a white screen and access only via FTP. The Code Snippets plugin catches fatal errors and automatically disables the problematic snippet, the site stays on its feet.

Do plugins for preventing duplicates help?

Specialized plugins like WooCommerce Duplicate Order Blocker work on a different level; they track duplicates by IP, email, or browser fingerprint at the checkout stage. This covers the double-click scenario that our scripts do not cover. However, plugins do not solve the problem of duplicates via add-to-cart URL and repeat purchases of a free product. The ideal option: the scripts from this article plus a blocker plugin on checkout.

Summary: three levels of duplicate protection

Three scripts cover three specific order duplication scenarios. Install all of them at once; each is responsible for its own area, and together they provide tight protection at the cart level.

If duplicates still appear after installation, look toward hosting and the payment gateway. A double click on the "Place order" button cannot be cured with code at the cart level; here you need either a blocker plugin or custom button protection via JavaScript.

Test the protection on a test site, then move it to production. Fifteen minutes to install three snippets saves hours of sorting out refunds.