Tag Archives: WooCommerce

Is Your WooCommerce Checkout Broken Right Now? How to Monitor It Before Customers Notice

Here’s a scenario that plays out on WooCommerce stores every day.

A plugin auto-updates overnight. The home page loads. Product pages load. Your uptime monitor shows a green tick. But the checkout throws a JavaScript error, or the payment step silently fails, or the cart empties itself on the way to checkout.

You find out two days later, when someone finally emails to say they couldn’t pay. Or you don’t find out at all, and just wonder why sales were slow that week.

Uptime is not the same as “customers can buy.” This guide explains what actually needs monitoring on a WooCommerce store, the free and paid ways to do it, and a practical setup for both single stores and agencies.

Why uptime monitoring isn’t enough

A typical uptime monitor requests your home page every few minutes and checks it returns a 200. That catches a crashed server. It misses almost everything that loses money in WooCommerce:

  • A broken cart or checkout page (PHP or JavaScript error on one page only)
  • A payment gateway that’s misconfigured, expired, or rejecting every payment
  • Webhooks being blocked, so paid orders never advance
  • The WooCommerce Store API failing, which block-based cart and checkout rely on
  • Background jobs stalling, so emails and stock updates stop
  • Caching serving a stale or broken checkout page
  • A sudden drop in orders because something upstream broke (ads, DNS for a payment script, a geo-blocking rule)

What to monitor on a WooCommerce store

Think about it in four layers.

Layer 1: Can people reach the buying pages?

Not just the home page, but shop, a product page, cart, and checkout, plus the Store API endpoints the block checkout uses. Each should load quickly and without errors.

Layer 2: Can they get to the payment step?

The real test is adding a product to the cart and reaching checkout. That’s where most plugin conflicts show up. This is called synthetic monitoring: a script behaves like a shopper on a schedule.

A word of caution: synthetic tests should not place real orders. Test orders pollute your reports, trigger fulfilment, and can create real charges. Good synthetic checks stop before payment.

Layer 3: Are orders that come in actually correct?

Orders can be created and still be wrong: stuck on Pending payment, charged twice, cancelled despite payment, missing a confirmation email. That needs integrity checks on the orders themselves. (See stuck pending orders) and double charges.)

Layer 4: Is order volume normal?

Sometimes nothing is technically “broken” by any test, but orders stop anyway. Comparing the current hour’s orders against comparable past hours (same day of week, same time of day) catches drops and spikes that point to a problem. The trick is avoiding false alarms on naturally quiet hours, which is why median-based comparisons work better than simple averages.

Alerts: the part people get wrong

Monitoring is useless if the alert goes to an inbox nobody reads, and equally useless if it fires so often everyone mutes it. Good alerting means:

  • The right channel: Slack or SMS for “checkout is down,” email for “weekly summary.”
  • Severity routing: urgent things wake someone, minor things wait.
  • Confirmation before alerting: one failed check could be a blip. Two in a row is a problem.
  • Throttling: one alert per issue, not one every five minutes.
  • A delivery log: so you can prove an alert was sent.

A free starting setup

You can get decent coverage for free:

  1. An uptime monitor on your home page *and* your checkout URL.
  2. WooCommerce → Status → Scheduled Actions checked weekly for past-due actions.
  3. ‘Tools → Site Health’ checked for loopback and REST API problems.
  4. [Ordafide Order Integrity for WooCommerce] (free) to scan orders for stuck payments, paid-but-cancelled orders, duplicate charges, refund mismatches, total mismatches, and customers who were never emailed. It also shows background queue and email delivery health, and sends an optional weekly summary when there’s something to report.
  5. A manual test purchase after every significant update, using a coupon to make it zero-cost or refunding immediately.

That covers layers 1 and 3 reasonably well. It leaves gaps in layers 2 and 4, and it relies on someone remembering to look.

A complete setup

[Ordafide Pro](https://www.premium-themes.co/ordafide-order-integrity-for-woocommerce/) fills those gaps:

  • Synthetic checkout monitoring every 15 minutes across shop, product, cart, checkout and the Store API, with response-time tracking. An optional deep mode adds a test product to a cart. It never creates orders, and a finding is raised only after two consecutive failures.
  • A signed heartbeat endpoint so an external monitor can confirm the store’s own checks are still running.
  • Order volume anomaly detection that compares the current hour with comparable past hours using median-based statistics, flags drops and spikes, and resolves itself when things return to normal.
  • Alerts via Slack, SMS (Twilio), signed webhooks (for Discord, Microsoft Teams, PagerDuty and similar) and email, with severity routing, per-check throttling, retries and a delivery log.
  • Read-only gateway reconciliation with Stripe, PayPal, Square, Mollie and Razorpay to catch payments with no order (see how to reconcile WooCommerce with Stripe and PayPal).

For agencies: monitoring client stores

If you maintain WooCommerce stores for clients, a broken checkout is an emergency you want to hear about before the client does, not from them.

What matters at agency scale:

  • One dashboard for all stores, not 20 logins.
  • Per-store alert routing, so the right person or client channel gets the right alert.
  • Silence detection, so a store that stops reporting entirely gets flagged rather than quietly looking “fine.”
  • Privacy. Clients’ order data shouldn’t be copied into a third-party service.
  • CLI access for scripted checks across many sites.

Ordafide’s free plugin includes WP-CLI commands (`wp ordafide scan`, `wp ordafide findings`, `wp ordafide health`, `wp ordafide digest`). Ordafide Pro adds a multi-store agency dashboard built on signed hourly snapshots that contain only counts and totals, not customer data. It flags any store that’s been silent for two hours, and the Agency licence adds per-store alert routing.

A monitoring checklist

  • [ ] Uptime checks on home and checkout
  • [ ] Synthetic check that reaches checkout without placing an order
  • [ ] Order integrity scan (stuck, duplicate, refund and total mismatches)
  • [ ] Background queue and email delivery monitored
  • [ ] Order volume compared against normal for the hour
  • [ ] Alerts routed to Slack/SMS for urgent issues, with throttling
  • [ ] Manual test purchase after major updates
  • [ ] For agencies: one dashboard and per-store routing

Frequently asked questions

How do I know if my WooCommerce checkout is broken?
Uptime monitors usually won’t tell you. Use synthetic monitoring that loads the cart and checkout on a schedule, plus order-volume anomaly detection and order integrity checks.

Can I monitor WooCommerce checkout without creating test orders?
Yes. Good synthetic monitoring checks shop, product, cart, checkout and the Store API, and can add an item to a cart, without ever placing an order. Ordafide Pro works this way.

How do I get Slack alerts for WooCommerce problems?
Use a monitoring plugin that supports Slack. Ordafide Pro sends Slack, SMS, email and webhook alerts with severity routing and throttling.

What’s the best way for an agency to monitor many WooCommerce stores?
A central dashboard that receives privacy-safe summaries from each store, flags stores that go silent, and routes alerts per store. WP-CLI commands help with scripted checks.

WooCommerce Order Stuck on Pending Payment (But the Customer Paid): Causes and Fixes

WooCommerce Order Stuck on Pending Payment (But the Customer Paid): Causes and Fixes

A customer emails you: I paid yesterday. Where’s my order?

You open WooCommerce and find their order sitting on Pending payment. Or worse, it says Cancelled, with a note that reads Unpaid order cancelled – time limit reached. Meanwhile, your Stripe or PayPal dashboard clearly shows the money arrived.

This is one of the most common, and most expensive, silent failures in WooCommerce. Nothing crashed. No error was shown. The store simply never found out that the payment succeeded.

This guide explains why it happens, how to confirm it, how to fix the orders you already have, and how to stop it from happening again.

Why WooCommerce thinks a paid order is unpaid

A WooCommerce sale isn’t one event. It’s a handoff between separate systems:

  1. The shopper’s browser submits checkout.
  2. WooCommerce creates the order in **Pending payment**.
  3. The payment gateway (Stripe, PayPal, Square, Mollie, Razorpay…) takes the money.
  4. The gateway tells WooCommerce the payment succeeded, either by redirecting the shopper back to your site or by sending a **webhook** (a server-to-server notification).
  5. WooCommerce moves the order to **Processing** and sends the confirmation email.

If step 4 fails, WooCommerce has no way of knowing that step 3 happened. Your store and your gateway now disagree, and WooCommerce doesn’t check.

The 6 most common causes

1. The webhook was blocked before it reached WooCommerce

This is the number-one cause. Something between the gateway and WordPress rejected the request:

  • Cloudflare Bot Fight Mode or a WAF rule challenging the gateway’s server
  • ModSecurity, Imunify360 or another host-level firewall rule
  • A security plugin that blocks or restricts the REST API or unknown POST requests
  • A redirect (HTTP to HTTPS, www to non-www) on the webhook URL. Webhooks generally don’t follow redirects
  • Maintenance mode returning a 503
  • Password protection on the site (common on staging copies that were pushed live)

2. The webhook secret is wrong

Each Stripe webhook endpoint has its own signing secret (it starts with ‘whsec_’). If you cloned the site, migrated hosts, restored a backup, or recreated the endpoint, the secret saved in WooCommerce may no longer match. The webhook arrives, the signature check fails, and the plugin rightly refuses to trust it, so the order never moves.

 3. The shopper closed the tab before returning

With off-site or redirect payment methods (PayPal, iDEAL, some wallets and bank methods), the shopper pays on the gateway’s page and is then sent back to your store. If they close the tab, lose signal, or their session cookie doesn’t survive the trip, the “return” leg never happens. If the webhook is also broken, nothing else will tell WooCommerce.

4. The webhook arrived but PHP crashed while processing it

The gateway’s log shows the delivery as successful, yet the order didn’t change. A fatal error in a plugin hooked into the order status change, or a timeout, can kill the request halfway through. Check ‘WooCommerce → Status → Logs’ for fatal errors around the payment time.

5. The site URL is wrong

If the ‘home’ or ‘siteurl’ options still point at an old or staging domain, the webhook URL registered with your gateway may be pointing somewhere that no longer exists.

6. Background processing is stalled

Some gateway plugins hand part of their webhook processing to WooCommerce’s background queue, Action Scheduler, to avoid race conditions. If WP-Cron or Action Scheduler isn’t running, that queued work sits there and the order never advances. (We cover this in detail in WooCommerce order emails not sending.)

Why paid orders get cancelled: the “Hold stock” timer

This catches out a lot of store owners.

Go to WooCommerce → Settings → Products → Inventory and look at Hold stock (minutes). The default is 60. When stock management is turned on, WooCommerce reserves stock for unpaid orders for that many minutes. When the timer runs out, it cancels any order still on Pending payment and adds the note *”Unpaid order cancelled – time limit reached.”*

That’s sensible for genuinely abandoned checkouts. But when the webhook is broken, a paid order looks exactly like an abandoned one. An hour later, WooCommerce cancels it and releases the stock. The customer has paid for an order your store now considers dead, and it won’t appear in your Processing list to be shipped.

If you use slow payment methods (bank transfers, some buy-now-pay-later providers), consider raising this value. Leaving the field blank turns off automatic cancellation entirely.

How to confirm a stuck order was actually paid

Before you change anything, verify the money. Never mark an order paid based on a customer’s word alone.

  1. Open the order and look in the Order notes and custom fields for a gateway reference: a Stripe Payment Intent ID (‘pi_…’) or charge ID (‘ch_…’), a PayPal transaction ID, and so on. Many gateways write this reference to the order before the payment is confirmed.
  2. Search for that reference in your gateway dashboard. Confirm the status is Succeeded / Completed, and that the amount and currency match the order.
  3. Check the gateway’s webhook log. In Stripe, open the Webhooks section of the Dashboard (under Developers / Workbench) and inspect the endpoint’s recent deliveries. A failed delivery shows the HTTP status your site returned, which tells you where to look:
    • 403: a firewall or security plugin blocked it
    • 301 / 302: a redirect on the webhook URL
    • 400 / 401: usually a signature or secret problem
    • 500: PHP crashed; check the error logs
    • 503: maintenance mode or the server was overloaded
  4. Check WooCommerce logs under ‘WooCommerce → Status → Logs’, filtering by your gateway’s log source.

How to fix the orders you already have

Once you’ve confirmed the payment succeeded:

  • Resend the event from the gateway (Stripe lets you resend a webhook event from the event’s page). If the underlying problem is fixed, WooCommerce will process it normally and update the order itself. This is the cleanest option because the gateway plugin records everything as it would have originally.
  • Or update the order manually. Change the status to Processing and add a private note with the transaction ID and how you verified it. For a cancelled order, check stock levels afterwards, because cancelling released the stock and restoring the order may not reduce it again.
  • Send the customer their confirmation. From the order’s Order actions dropdown, choose Email invoice / order details to customer, and add a short apology. Customers who pay and hear nothing often open a dispute.

How to fix the root cause

  • Test the webhook URL. For the official WooCommerce Stripe plugin, the endpoint is shown in ‘WooCommerce → Settings → Payments → Stripe’ and usually looks like ‘https://yourstore.com/?wc-api=wc_stripe’. An unsigned test request should reach the plugin and be rejected with a 400, not blocked with a 403.
  • Allow gateway traffic through your firewall. Create Cloudflare/WAF rules that skip challenges for the webhook path, and check your security plugin’s REST API and firewall settings.
  • Re-enter the signing secret after any migration, clone or restore. For PayPal Payments, use the plugin’s option to resubscribe webhooks.
  • Make sure background jobs run. Check ‘WooCommerce → Status → Scheduled Actions’ for a pile of past-due actions.
  • Send a test event from the gateway (or the Stripe CLI) and confirm it returns a 200 and the order updates.

Catching this automatically instead of from angry emails

The painful part of this problem is not fixing it. It’s *noticing*. A broken webhook can quietly affect dozens of orders before a customer complains, and by then many of them have been auto-cancelled.

We built Ordafide Order Integrity for WooCommerce (free on WordPress.org) to scan your recent orders for exactly this pattern:

  • Stuck payment: orders left on Pending payment or Failed even though the gateway wrote a payment reference on them.
  • Paid but cancelled: orders with a confirmed payment date sitting in Cancelled or Failed, with nothing refunded.

Each finding is sorted by the money at risk and comes with the evidence and what to check in your gateway before you touch anything. If WooCommerce itself recorded a confirmed payment date on an order, Ordafide can restore it to Processing with one click. That change is written to an activity log and can be undone. It never captures, refunds or voids payments, and it sends no data to any third party.

For stores that need certainty rather than inference, Ordafide Pro adds read-only gateway reconciliation with Stripe, PayPal, Square, Mollie and Razorpay. It compares what your gateway actually collected against what WooCommerce recorded, including payments that never produced an order at all, and can alert you on Slack, SMS, email or webhook.

Quick checklist

  • [ ] Verify the payment in the gateway before changing any order
  • [ ] Check webhook delivery logs and note the HTTP status code
  • [ ] Fix the firewall, redirect, secret or URL that caused it
  • [ ] Confirm Action Scheduler isn’t backed up
  • [ ] Review Hold stock (minutes) for your payment methods
  • [ ] Restore affected orders and resend confirmation emails
  • [ ] Set up ongoing monitoring so you hear about it before customers do

Frequently asked questions

Why does my WooCommerce order say Pending payment when the customer paid?
Because WooCommerce never received confirmation from the payment gateway. The most common reason is a blocked or failed webhook, often caused by a firewall, security plugin, redirect or wrong webhook secret.

Why did WooCommerce cancel a paid order?
The *Hold stock (minutes)* setting cancels orders that stay on Pending payment past the time limit (60 minutes by default). If the payment confirmation never arrived, a paid order looks unpaid and gets cancelled.

Is it safe to manually mark a Pending order as Processing?
Yes, but only after confirming in your gateway dashboard that the payment succeeded for the right amount. Add a private note recording the transaction ID.

How do I stop it happening again?
Fix the webhook path, keep background jobs running, and monitor for paid orders that don’t advance. A free tool like Ordafide flags these orders automatically.

—

*Have a stuck-order story we didn’t cover? Ask on the Ordafide support forum.

WooCommerce Order Emails Not Sending? Check Action Scheduler, WP-Cron and SMTP

When WooCommerce order emails stop arriving, the store looks perfectly healthy. Orders come in. The dashboard loads. There’s no error message anywhere.

The only symptom is your inbox filling up with *”Did my order go through?”* messages, and sometimes chargebacks from customers who assumed they’d been scammed.

This guide walks through the causes in the order you should check them, from the quick settings mistakes to the silent background-queue failures that catch out even experienced developers.

First: is it one email, one customer, or everything?

The pattern tells you where to look:

  • | Symptom | Most likely cause |
  • |—|—|
  • | One customer didn’t get their email | Typo in their address, or it went to spam |
  • | One email type stopped (e.g. *Completed order*) | That email was disabled in settings |
  • | No emails at all, from the moment you migrated | Mail sending is broken (no SMTP) |
  • | Emails arrive hours late, or in bursts | Background queue (Action Scheduler / WP-Cron) is stalled |
  • | Admin emails arrive but customer emails don’t | Customer emails disabled, or a recipient/plugin issue |

Step 1: Check the email settings

Go to WooCommerce → Settings → Emails. Every email (*Processing order*, *Completed order*, *Customer invoice*, *New order* and so on) has its own enable toggle. Plugins, staging tools and well-meaning colleagues sometimes switch these off.

While you’re there, check the “From” address. If it uses a domain your mail service isn’t authorized to send for, receiving servers may reject or junk the message.

Step 2: Check that WordPress can send email at all

By default, WordPress sends mail with PHP’s `mail()` function. Many hosts restrict it, and messages sent this way often fail SPF/DKIM checks and land in spam, or are silently discarded with no error.

The fix is to send through a proper mail provider over SMTP or an API using an SMTP plugin, and to set up SPF, DKIM and DMARC records for your sending domain. Then send a test email from the SMTP plugin.

If test emails work but order emails still don’t, keep going. The problem is upstream of the mail server.

Step 3: Check the background queue (the silent killer)

WooCommerce relies on Action Scheduler, a background job queue, for a lot of order-related work, and some payment gateways hand parts of their payment confirmation to it. Action Scheduler is triggered by WP-Cron. And WP-Cron, by default, only runs when someone visits your site.

When this chain breaks, work queues up and doesn’t run. Order status changes that depend on queued work don’t happen, so the emails tied to those status changes don’t send either. There is no error and nothing on screen changes.

How to check

Go to WooCommerce → Status → Scheduled Actions and open the Pending tab.

  • A handful of actions scheduled in the future is normal.
  • Dozens or hundreds of actions that are past due means nothing is running the queue.
  • A large number in the Failed tab means jobs are running but crashing. Open one to see the error.

Common reasons the queue stops

  • DISABLE_WP_CRON’ is set to ‘true’ in ‘wp-config.php’, usually by a host or performance plugin, but no real server cron job was set up to replace it.
  • Loopback requests are blocked. WP-Cron works by your site making an HTTP request to itself. A firewall, basic-auth password, or DNS issue that blocks this kills the scheduler. `Tools → Site Health` reports loopback failures.
  • Low traffic. On a quiet store, if no one visits, WP-Cron doesn’t run.
  • A huge backlog. On busy stores the Action Scheduler tables can grow to hundreds of thousands of rows, and the queue can’t keep up.
  • A fatal error in one job that keeps failing and retrying.

How to fix it

1. Set up a real cron job on your server (most hosts offer this in the control panel) to run WP-Cron every few minutes, for example:
‘*/5 * * * * wget -q -O – “https://yourstore.com/wp-cron.php?doing_wp_cron” >/dev/null 2>&1’
or, with WP-CLI: ‘wp cron event run –due-now’
2. Then disable visitor-triggered cron with ‘define( ‘DISABLE_WP_CRON’, true );’ so the two don’t compete.
3. Clear the backlog by running it, not deleting it. Pending actions may include real customer emails and order updates. With WP-CLI you can run ‘wp action-scheduler run’. On very large backlogs, ask your host for help.
4. Fix loopback if Site Health reports it failing: whitelist your own server IP in the firewall, or remove basic-auth from production.
5. Investigate failed actions and fix the plugin causing them.

Step 4: Resend the emails customers missed

Once the cause is fixed, the emails that never went out won’t magically send themselves. For each affected order, open it, choose Order actions → Email invoice / order details to customer (or *Resend new order notification* for the admin copy), and update.

The hard part is working out *which* orders were affected. WooCommerce doesn’t give you an easy per-order record of whether each customer email was actually handed to your mail server.

Watching this automatically

Ordafide Order Integrity for WooCommerce() is a free plugin that watches the two things that make everything else fail at once:

  • Background job queue health: how far behind Action Scheduler is, how many jobs failed, and the most likely cause in plain language, plus the WordPress scheduler’s status.
  • Order email delivery: every WooCommerce email recorded against its order, with the result the mail server returned, and a one-click resend.

Its Customer never emailed check flags paid orders where no customer email reached your mail server, or where every attempt was rejected. A loopback test checks whether your site can call itself (it only calls your own site, and only when you press the button), and an optional weekly summary email tells you when there’s something to look at.

Resending is one of only two repairs the plugin can make, and every resend is written to an activity log. Ordafide sends no data to third parties and has no account or licence key.

If you’d rather be alerted the moment the queue stalls instead of finding out in a weekly summary, Ordafide Pro(https://www.premium-themes.co/ordafide-order-integrity-for-woocommerce/) adds Slack, SMS, email and webhook alerts, plus synthetic checkout monitoring every 15 minutes.

Quick checklist

  • Every needed email is enabled in WooCommerce → Settings → Emails
  • Mail goes through SMTP or an email API, with SPF/DKIM/DMARC set up
  • No pile of past-due actions in Scheduled Actions
  • A real server cron runs WP-Cron if `DISABLE_WP_CRON` is set
  • Site Health shows loopback requests working
  • Affected customers have had their emails resent

Frequently asked questions

Why are my WooCommerce order emails not sending?
The most common causes are disabled email settings, WordPress using PHP ‘mail()’ without SMTP, and a stalled background queue (Action Scheduler / WP-Cron) that stops order processing from completing.

What does “past-due actions” mean in WooCommerce?
Scheduled actions that should already have run but haven’t. A large number means WP-Cron or Action Scheduler isn’t being triggered, often because ‘DISABLE_WP_CRON’ is set without a server cron, or loopback requests are blocked.

Should I delete past-due scheduled actions?
Generally no. They can include real customer emails and order updates. Fix the cause and let them run.

How do I know which customers didn’t get their confirmation email?
WooCommerce doesn’t keep an easy per-order delivery record. A plugin that logs each email against its order, such as Ordafide, shows exactly which orders were never emailed and lets you resend in one click.

WooCommerce Customer Charged Twice? How to Find and Prevent Duplicate Orders and Double Charges

WooCommerce Customer Charged Twice? How to Find and Prevent Duplicate Orders and Double Charges

Few emails damage trust faster than “You charged my card twice.”

Double charges are rarely caused by one dramatic bug. They come from ordinary things: a slow checkout, an impatient click, a firewall that retries a request, a payment plugin that times out and tries again. Each one is rare. Across hundreds of orders a month, they add up, and each one that goes unnoticed becomes a refund request, a chargeback, or a one-star review.

This guide covers what causes duplicate orders in WooCommerce, how to find every one in your store, how to refund them without breaking your records, and how to prevent them.

Two different problems that look the same

It helps to separate two situations:

  1. Duplicate orders: two WooCommerce orders for the same basket. The customer may have been charged once or twice.
  2. Duplicate charges: the customer’s card was charged twice. There may be one order or two.

The worst case is a second charge with no matching order at all. WooCommerce has nothing to show you, so you only find it in your gateway dashboard, or when the customer’s bank statement arrives.

What causes double charges in WooCommerce

Multiple clicks on “Place order”

On a slow checkout, shoppers click again. WooCommerce’s classic checkout blocks the form while it processes, but slow pages, custom themes, page builders and some checkout plugins can weaken that protection. A second click can create a second order and, depending on the gateway, a second payment.

The page was refreshed or the back button was used

A shopper who refreshes on the payment step, or goes back and resubmits, can create a second order with an identical basket.

Request replays by a firewall, proxy or host

Some firewalls, CDNs and reverse proxies resend a POST request when the first one is slow to respond, assuming it failed. If your checkout takes several seconds (often because of third-party API calls for tax, shipping rates or fraud checks), the replayed request can process as a brand-new checkout. Sucuri, in particular, has published guidance on configuring it for WooCommerce for this reason.

Payment plugin retries after a timeout

If the gateway responds slowly, a payment plugin may treat the request as failed and retry. If the first attempt actually succeeded, the customer is charged twice.

Subscription renewal retries

Renewal logic that retries a “failed” payment which actually went through can create two renewal orders and two charges.

One transaction ID attached to two orders

Occasionally a plugin conflict attaches the same gateway transaction to two different orders. You see two “paid” orders but only one payment. This inflates your revenue reports and can lead to shipping two parcels for one payment.

How to find every duplicate charge

Step 1: Look for near-identical orders

In WooCommerce → Orders, sort by date and look for the same customer email, same total and same items within a few minutes of each other. On a small store this is tedious. On a busy store, it isn’t realistic to do by hand every week.

Step 2: Check transaction IDs

Open each suspected pair and compare the gateway transaction IDs. Two orders with the same transaction ID usually means one payment, two orders. Two orders with different transaction IDs means two real payments.

Step 3: Compare against the gateway

Search your Stripe or PayPal dashboard for the customer’s email. Count the successful payments for that basket and compare against the orders in WooCommerce. This is the only way to catch a second charge that never produced an order.

Step 4: Check your logs

‘WooCommerce → Status → Logs’ and your server access logs can show whether two checkout requests arrived close together. That tells you whether it was the shopper, a replay, or a plugin retry, and that decides the fix.

How to refund a double charge without breaking your records

The cleanest fix keeps WooCommerce and your gateway in agreement:

  1. Decide which order to keep. Usually the first one.
  2. Refund the duplicate from inside WooCommerce, using the Refund button on the duplicate order and choosing Refund via gateway. This sends the refund to the gateway and records it in WooCommerce, so stock, revenue and reports stay correct.
  3. Avoid refunding only in the gateway dashboard. A refund made directly in Stripe or PayPal may never be recorded in WooCommerce. The order still looks paid, your revenue is overstated, and stock may be wrong. (See how to reconcile WooCommerce with Stripe and PayPal.)
  4. Make sure the duplicate isn’t shipped. Once fully refunded, the order should move to Refunded. A fully refunded order still sitting in Processing is a classic way to ship an order you didn’t get paid for.
  5. Tell the customer. Let them know it was spotted, refunded, and how long their bank usually takes to show it.

How to prevent duplicate orders

  • Speed up checkout. The slower the checkout, the more people click twice and the more likely a proxy replays the request. Audit plugins that call external APIs during checkout.
  • Don’t break the built-in submit protection. If you’ve customized the checkout or use a page builder, test what happens when you double-click Place order on a slow connection (browser DevTools can throttle the network).
  • Review your firewall and CDN. Make sure nothing retries POST requests to checkout or payment endpoints.
  • Keep your gateway plugin updated. Retry and idempotency handling improves over time.
  • Test with block checkout and classic checkout separately if you’ve switched between them.
  • Monitor continuously. Prevention reduces duplicates. It doesn’t eliminate them.

Catch duplicates automatically

Ordafide Order Integrity for WooCommerce is a free plugin that scans your recent orders and flags problems in plain sentences, sorted by money at risk. Its Duplicate charge check looks for:

  • one gateway payment reference attached to two orders, and
  • the same customer charged twice for an identical basket within minutes.

It also runs a Refund mismatch check that catches orders marked Refunded with no refund record, refunds larger than the order total, and fully refunded orders still marked Processing, so a refunded duplicate doesn’t get shipped anyway.

The duplicate window and other thresholds are configurable, findings you’ve reviewed can be ignored, and the plugin never captures, refunds or voids a payment. Everything runs on your own server.

The one case a store-side scan can’t see is a charge with no order. Ordafide Pro covers it with read-only reconciliation against Stripe, PayPal, Square, Mollie and Razorpay, flagging money with no order, paid orders with no payment, and amount disagreements.

Frequently asked questions

Why did my customer get charged twice on WooCommerce?
Usually a double click on a slow checkout, a page refresh during payment, a firewall or proxy replaying the checkout request, or a payment plugin retrying after a timeout.

How do I refund a duplicate WooCommerce payment?
Refund it from the duplicate order in WooCommerce using Refund via gateway, so the refund is recorded in both systems.

Can a customer be charged twice with only one WooCommerce order?
Yes. If the second charge’s order was never created, WooCommerce shows nothing. Only comparing against the gateway, manually or with a reconciliation tool, will find it.

Does WooCommerce prevent duplicate orders on its own?
It has basic protection against repeat form submission, but it can’t stop request replays, plugin retries or every theme customization. Monitoring is the reliable safety net.

How to Reconcile WooCommerce Orders with Stripe and PayPal (and Find Money With No Order)

Your WooCommerce sales report says one number. Your Stripe balance says another. Your PayPal activity says a third. Your accountant wants to know which one is true.

Here’s the uncomfortable fact behind that gap: WooCommerce records what it believes happened. Your payment gateway records what actually happened. They’re two separate sources of truth, and nothing in WooCommerce continuously checks that they agree.

Most of the time they do. When they don’t, the difference is usually real money: customers who paid and never got their order, refunds your reports don’t know about, or stock that’s wrong because a refund never reached WooCommerce.

This guide gives you a practical reconciliation process you can run monthly (or weekly on busier stores), the five mismatches to look for, and how to fix each one.

Why WooCommerce and your gateway drift apart

  • Webhooks fail. The gateway tells WooCommerce about payments and refunds via webhooks. A firewall, a wrong secret, or a crash means WooCommerce never hears about it.
  • People work in two places. Staff issue refunds in the Stripe or PayPal dashboard because it’s faster. WooCommerce never finds out.
  • Checkouts break halfway. A payment succeeds but the order is never created or never updated.
  • Plugins modify totals. Coupons, fees, currency switchers and tax plugins can leave the stored order total different from the amount actually charged.
  • Disputes happen at the gateway. Chargebacks are raised with your gateway, not your store.

The 5 mismatches to look for

1. Money with no order

The gateway shows a successful payment, but there’s no corresponding WooCommerce order. This is the hardest to find, because WooCommerce has nothing to show you. It’s invisible by definition from inside the store. Causes include a checkout that crashed after the charge, or a duplicate charge whose order was never created.

Fix: contact the customer, then either create the order manually (so you fulfil it) or refund the payment.

2. Paid orders with no payment

WooCommerce shows an order as Processing or Completed, but there’s no matching successful payment in the gateway. Causes include an order manually set to Processing by mistake, a payment that was authorized but never captured, or one transaction attached to two orders.

Fix: stop fulfilment until you’ve confirmed payment, then contact the customer or cancel.

3. Amount disagreements

The order total in WooCommerce doesn’t match what the gateway collected. Small persistent differences often come from currency rounding. Bigger ones usually come from a coupon, fee or shipping plugin misfiring, or a manual order edit after payment.

Fix: decide which figure is right, then correct the order (or issue a partial refund) and add a note explaining why.

4. Refunds made in the gateway, missing in WooCommerce

Someone refunded in Stripe or PayPal directly, and WooCommerce still shows the order as fully paid. Your revenue is overstated, stock that came back was never restocked, and the order could even be shipped again.

Fix: record the refund in WooCommerce. Use *Refund manually* on the order (so WooCommerce doesn’t try to send the money a second time), tick *Restock refunded items* if the stock came back, and add a note referencing the gateway refund ID.

5. Refund status mismatches inside WooCommerce

These you can catch without the gateway at all: an order set to *Refunded* by hand with no refund actually issued (the customer never got their money), refunds larger than the order total, and fully refunded orders still sitting in *Processing*.

A manual reconciliation process

You can do this with exports and a spreadsheet. It takes time, but it works.

Step 1: Pick a period and export both sides

  • WooCommerce: export orders for the period with order number, date, status, total, payment method and transaction ID. Include refunds.
  • Stripe: export payments (and refunds) for the same period from the Payments section. Use the Payment Intent or charge ID.
  • PayPal: download an activity report for the period from your business account’s reports.

Use the same timezone on both sides, or orders near midnight will appear to be missing.

Step 2: Match on transaction ID, not amount

Match each gateway payment to a WooCommerce order using the transaction ID WooCommerce stores on the order. Matching by amount and date produces false matches whenever two customers buy the same thing.

Step 3: Sort the leftovers into the five buckets

Anything that doesn’t match cleanly belongs in one of the five categories above. Note the amount at stake for each.

Step 4: Fix the most expensive first

Customers who paid and received nothing come first. They’re the ones about to raise a dispute.

Step 5: Fix the cause

A mismatch is a symptom. If you find several stuck payments, check your webhooks (see [WooCommerce order stuck on pending payment](/blog/woocommerce-order-stuck-pending-payment/)). If you find gateway-only refunds, agree a rule with your team: refunds are always issued from the WooCommerce order screen.

Where manual reconciliation falls down

  • It’s slow. Exports, lookups and matching easily take hours per month.
  • It’s late. A monthly check means a customer may have waited weeks.
  • It’s fragile. Partial refunds, multiple currencies, and multiple gateways make spreadsheets messy fast.
  • It doesn’t scale to agencies. Doing this across 10 or 20 client stores isn’t realistic.

Automating it

Start free. [Ordafide Order Integrity for WooCommerce] runs the store-side checks automatically: stuck payments, paid-but-cancelled orders, duplicate charges, refund mismatches and order totals that don’t add up. Findings are sorted by money at risk, each with evidence and next steps, and the plugin sends nothing off your server.

Then reconcile against the gateway. [Ordafide Pro] connects to Stripe, PayPal, Square, Mollie and Razorpay with read-only API keys and continuously compares what the gateway collected against what WooCommerce recorded. It detects:

  • money with no order
  • paid orders with no payment
  • amount disagreements
  • refunds missing in WooCommerce

Pro also keeps a full event log with CSV/JSON audit export over any date range (with secrets removed), which is useful to hand to an accountant. Because credentials are read-only, Ordafide can’t capture, refund or void anything at the gateway. It tells you what’s wrong and links you to the order.

A sensible reconciliation rhythm

  • | Store size | Store-side checks | Gateway reconciliation |
  • |—|—|—|
  • | Under ~100 orders/month | Weekly | Monthly, manually |
  • | Hundreds of orders/month | Continuous (automated) | Weekly or continuous |
  • | Thousands/month, or multiple gateways | Continuous | Continuous, with alerts |\
  • | Agencies managing client stores | Continuous on every store | Continuous, with per-store alerts |

Frequently asked questions

Why doesn’t my WooCommerce revenue match Stripe?
Common reasons are failed webhooks (payments or refunds WooCommerce never heard about), refunds issued directly in Stripe, Stripe fees (WooCommerce shows gross, Stripe payouts are net), timezone differences, and plugins that changed order totals.

If I refund in Stripe, does WooCommerce update?
Sometimes. It depends on your gateway plugin and whether its webhook is working. Refunding from the WooCommerce order screen is the reliable way to keep both in sync.

How do I find payments that have no WooCommerce order?
Only by comparing the gateway’s payment list against your orders, either manually by transaction ID or with a reconciliation tool like Ordafide Pro.

Is it safe to give a plugin my Stripe API key?
Use a restricted, read-only key wherever your gateway supports it. Ordafide Pro is designed for read-only credentials and cannot move money.