← Back to blog

How to test a WooCommerce checkout when the part that breaks is not the checkout

The SandyWP team 7 min read

Place the order on a disposable copy of the real store, then check four things the checkout page never shows you: whether the gateway webhook actually landed, whether the order left pending payment, what email went out, and which order table the data was written to. A test card clearing is the easiest part of the flow and the least likely thing to be broken.

Every guide that ranks for this covers the same ground. Enable test mode, use 4242 4242 4242 4242, place an order, check it appears. That validates the twenty seconds a customer spends on the form and none of the machinery behind it.

What a passing test order does not prove

The checkout page collects data and hands off to a gateway. Almost everything that decides whether the order is real happens afterwards, asynchronously, in code paths a browser test never touches.

Here is the split, plainly.

The checkout proves: the form renders, validation fires, shipping and tax calculate, the coupon applies, the gateway accepts the card.

The checkout does not prove: the order transitions out of pending payment, stock decrements, the customer receives a readable email, your fulfilment integration fires, or the order is stored where your other plugins look for it.

Those four are where real stores break.

1. The order status comes from the webhook, not the browser

When a card clears, the customer is redirected to a thank-you page. That redirect is not what marks the order paid. A gateway event, delivered server to server, is.

If the webhook does not arrive, the payment succeeds at the processor and the order sits in pending payment or gets cancelled by the hold-stock timer. This is the single most reported WooCommerce Stripe failure, and it is invisible from the checkout page. There is an open issue on the WooCommerce repository for exactly this shape of it (woocommerce-gateway-stripe#5691).

So the test is not "did the card go through". The test is "did the order status change on its own, without me touching it".

Place the order, then walk away from the tab. Come back and look at the order status in wp-admin. If you had to refresh the gateway settings or click something to make it update, your webhook is not working.

2. Test mode and live mode are separate webhook wiring

This is where a lot of test-versus-production drift comes from, and the Stripe documentation states it directly: "Stripe generates a unique secret key for each endpoint. If you use the same endpoint for both test and live API keys, the secret is different for each one" (Stripe webhooks).

Two consequences follow.

A test-mode pass says nothing about your live-mode endpoint, because it is a different endpoint with a different signing secret. And a live-mode failure will not reproduce in test mode, because you are exercising different wiring.

Three more details from the same page that catch people out on a test environment specifically:

  • Endpoints "must be publicly accessible HTTPS URLs". A local WordPress install cannot receive one at all without a tunnel.
  • "We consider redirect responses to webhook requests as failures." A copy of your store that canonical-redirects http to https, or non-www to www, will return a 301 and the delivery is recorded as failed even though the site is up.
  • Retries are shorter in a sandbox: "We retry event deliveries created in a sandbox three times over the course of a few hours", against up to three days in live mode.

The redirect one is worth dwelling on. It is the most common reason a webhook fails on a copied store and not on the original, and it produces a failure that looks like a network problem rather than a URL problem.

A hosted sandbox helps here for one boring reason: it has a real public HTTPS URL out of the box, so you can paste it straight into the gateway dashboard as a test-mode endpoint. There is no tunnel to keep alive and no self-signed certificate for the gateway to reject. We wrote about the general version of that problem in testing a WordPress webhook when the sender needs a public URL.

3. A copy of your store will email your real customers

This is the risk that turns a checkout test into an incident, and it is almost absent from the guides that rank.

Clone a production store and you clone the customer table, the pending subscription renewals, the abandoned-cart plugin and its schedule, and the review-request emails queued against past orders. Then WP-Cron starts running on the copy.

Your test order sends one email. The environment around it can send hundreds, to people who bought something from you last month.

Two things make this safe, and you want both. Confirm no mail can leave the copy before you place a single order. Then still check what your test order tried to send, because "no receipt arrived" and "the receipt was blank" are different bugs and you cannot tell them apart if nothing was captured.

On SandyWP that is what Email Log does. wp_mail() is intercepted before it leaves, so with no SMTP configured nothing reaches a real address, and every message still appears in the log with its recipient, headers and rendered HTML body. You open the order confirmation and read it exactly as the customer would.

That matters more than it sounds for WooCommerce, because order emails are template-driven and a broken template usually renders as a valid email rather than an error. Placing the order tells you the mail fired. Reading it tells you the line items are there.

4. Check which table the order was written to

High-Performance Order Storage moves orders out of posts and postmeta into four dedicated tables: _wc_orders, _wc_order_addresses, _wc_order_operational_data and _wc_orders_meta (WooCommerce developer docs).

A plugin that queries postmeta directly for order data will find nothing once HPOS is authoritative. The order looks perfect in wp-admin and your fulfilment or accounting integration sees an empty result.

Compatibility mode is what makes this hard to test. With it enabled, WooCommerce "will sync the order data between the posts/postmeta and the WooCommerce order tables", so both places are populated and a legacy plugin keeps working. Your checkout test passes on a store that has not actually committed to HPOS yet.

So the test is: turn compatibility mode off on the copy, place an order, and exercise the integration, not the order screen. If a plugin is genuinely incompatible WooCommerce will tell you, because it disables the HPOS option and lists the offenders under WooCommerce, Settings, Advanced, Features.

Do this on a copy, not on the live store

There is a second, quieter reason not to test checkout in production, separate from the risk of a real charge.

Test orders are permanent data. They decrement stock, they take a number in the order sequence, they consume coupon usage limits, they enter WooCommerce Analytics, and if you deleted them your revenue reporting for that period is wrong either way.

A disposable copy has none of that cost, and you can throw it away and make another one when you have broken it badly enough to learn something.

The rough shape of a run on SandyWP:

  1. Clone the live store with the Cloner plugin, which copies the files and the database, so you are testing the real product catalogue and the real plugin stack rather than a clean install with three sample products.
  2. Confirm mail is captured, not delivered, and that any abandoned-cart or review-request plugin is deactivated before you touch anything else.
  3. Point a test-mode webhook endpoint at the sandbox URL and turn on WP_DEBUG_LOG from Debug mode so a fatal in a payment hook lands somewhere you can read.
  4. Place the order, leave it, and come back to check the status changed by itself.
  5. Read the order email in the log, and check the order in the database over SSH with wp wc order get <id> --user=1, not just in wp-admin.
  6. Save the configured store as a template so the next test starts from step 4 instead of step 1.

What a sandbox still cannot tell you

Being honest about this is the difference between a test that means something and a test that gives you confidence you have not earned.

A sandbox will not exercise your live-mode keys or your real bank's 3D Secure behaviour, and test-mode 3DS is a simulation. It will not reproduce your host's page cache, CDN or WAF, all of which sit in front of the checkout in production and are a documented cause of stuck checkouts and invalid nonces. It will not reproduce concurrency, so a stock race condition under real load will not appear. And any service keyed to your domain, such as a tax calculation account, a carrier rate account or a fraud tool, will behave differently or not at all.

For those, the answer is a real staging site on the same host as production, or a small number of carefully watched live orders with a real card that you then refund. A sandbox is the right tool for the logic, the data and the integrations. It is the wrong tool for the infrastructure.

The useful mental model: test the order lifecycle on a copy, and test the network path on the real host. Most teams do neither and test the card form on both.