WooCommerce

Duplicate WooCommerce Orders: Causes and a Permanent Fix

Customers charged twice or two orders for one purchase. How the checkout creates orders, the retries and webhook overlaps that duplicate them, and the fix.

5 min read
WooCommerceCheckoutDebugging
995 words5 min read

Two kinds of duplicate

Two orders, one payment. The customer paid once; the store shows two orders, one paid and one pending or failed. Annoying, confusing for fulfilment, harmless to the customer.

Two orders, two payments. The customer was charged twice. This is the one that generates refund requests, chargebacks and one-star reviews, and it needs fixing today.

Both come from the same set of causes. Understanding how an order is created explains them.

How the checkout creates an order

  1. The customer's cart is held in the session.
  2. When they reach the checkout, the block checkout creates a draft order in the background (checkout-draft status) and updates it as they type.
  3. On Place Order, the Store API's checkout endpoint converts the draft to a pending order and asks the gateway to process payment.
  4. The gateway either completes synchronously or redirects the customer and confirms later via webhook.
  5. The order moves to processing or completed.

Duplicates happen when step 3 runs twice for the same cart, or when step 4 and 5 disagree about which order a payment belongs to.

Cause 1: the customer submits twice

A slow response after Place Order, the button not disabled, the customer clicks again. Two checkout requests, two orders, and if the gateway processed both, two charges.

The block checkout disables the button and uses an idempotency mechanism for the draft order, which makes this much rarer than on the shortcode checkout. It still happens when JavaScript is broken by another plugin or when the customer refreshes a stuck page.

Fix: look for console errors on the checkout and remove the cause. Make sure the checkout page loads quickly so the customer never gets the chance; a checkout that takes five seconds to respond invites the second click.

Cause 2: a redirect gateway and an impatient customer

Customer pays at PayPal or a 3D Secure page, is slow to return or closes the tab, comes back to the store, sees the cart still full (the session has not been cleared because the return never completed), and checks out again. The webhook for the first payment arrives later. Two paid orders.

Fix: the gateway's return URL and webhook handling must clear the cart and mark the original order promptly. Check the gateway's log for webhook delivery delays or failures. Check that the webhook endpoint is not blocked or cached, which delays the confirmation that would have emptied the cart.

Cause 3: webhook delivered more than once, matched to the wrong order

Gateways retry webhooks. A webhook handled slowly (the store times out) gets retried, and if the handler is not idempotent it may create or complete a second order. Some poorly written gateway integrations create an order on webhook receipt if they cannot find one, and a mismatch in the lookup key produces a new one.

Fix: update the gateway plugin; idempotent handling is table stakes and recent versions have it. Make the webhook endpoint fast: exclude it from anything that slows PHP, and make sure the server responds within the gateway's timeout. Check the order notes on the duplicates; they usually say which webhook event created them.

Cause 4: draft orders converting twice

A bug class specific to the block checkout, especially with certain plugins hooking into order creation, where the draft order is not correctly reused and a fresh order is created on each checkout update. Shows up as many pending orders per customer, not necessarily paid.

Fix: update WooCommerce and the checkout-related plugins; several such bugs have been fixed in point releases. Look for plugins that hook woocommerce_checkout_create_order or the Store API order creation and test with them disabled on staging.

Cause 5: two checkout forms on one page

A checkout page with both the block and the shortcode, or a theme that renders a mini-checkout in a sidebar, or a page builder widget duplicating the form. Each submission path creates orders independently.

Fix: one checkout on the page. Inspect the page for a second form.

Cause 6: a caching or optimisation layer breaking nonce or session handling

A page cache serving the checkout, or an optimisation plugin deferring WooCommerce's scripts, breaks the session and idempotency handling that prevents duplicates. Customers get a stale checkout, submit, get an error, submit again.

Fix: exclude cart, checkout and /wc/store/ from caching; exclude WooCommerce's scripts from combination and deferral.

Diagnosing an actual duplicate

For each pair:

  1. Timestamps. Seconds apart means a double submit or double webhook. Minutes apart means the customer came back and checked out again.
  2. Order notes. Both orders record how they were created and what the gateway said. Read them.
  3. Gateway dashboard. One charge or two? Match transaction IDs to orders.
  4. Gateway log in WooCommerce, Status, Logs, around those timestamps. Retries and failures are recorded here.
  5. Session. Same customer, same cart contents, same session? Confirms the cart was not cleared.

The pattern across several pairs identifies the cause.

The permanent fix

Almost always: update WooCommerce and the gateway; make the checkout and webhook endpoint fast; exclude both from caching and script optimisation; ensure the return flow clears the cart. Then place several test orders through each gateway, including an abandoned redirect that you complete late, and confirm one order per payment every time.

Cleaning up

For two-orders-one-payment, cancel the unpaid duplicate; WooCommerce's pending-order cleanup will do it after an hour if left. For two payments, refund the duplicate charge promptly from the order screen so the gateway records match, and email the customer before they email you.

Where this fits

Duplicate orders are on the short list of things we check on every store that comes to us for WooCommerce work, because they are a symptom of the checkout being slow, cached or broken by another plugin, and those are the same faults that cost sales in quieter ways. Fixing the duplicate usually fixes those too.

All writingHire me for this