Tag Archives: PayPal

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.

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). 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.