Loading tutorials…
Loading tutorials…
Customer says 'checkout did not work.' You test and it seems fine. The order is stuck in 'Pending payment' or never created at all. This is the diagnostic sequence Woo specialists run — payment, webhook, plugin conflict, theme override, Blocks vs Classic.
Who this is forWooCommerce owners getting customer-reported checkout failures more than 2-3 times per month. The cost of unsolved checkout bugs is brutal — every failed checkout is a lost order plus a complaint ticket plus potential negative review.
What you'll need
Step 1
Get the customer's browser/device, the exact step that failed, any error message, and time of attempt. Without this, you are debugging blind.
When a customer reports a checkout failure, ask: (a) device + browser (iPhone Safari, Android Chrome, desktop Firefox), (b) payment method attempted (card, Apple Pay, PayPal), (c) exact error message visible (screenshot if possible), (d) approximate time + timezone of the attempt, (e) the cart contents.
In WooCommerce → Orders → search by customer email. Sometimes the order WAS created but stuck in "Pending payment." That tells you the failure is post-payment-success — likely webhook or plugin.
In WooCommerce → Status → Logs → filter for "fatal-errors" log file. PHP fatal errors during checkout produce log entries here with stack traces.
In your payment processor: Stripe → Payments → failed payments (or Disputes if it got that far). PayPal → Activity → Transactions filtered by Failed. The processor side often shows the actual decline reason.
Reproduce the failure yourself. Use the same browser, the same product, the same payment method. If you cannot reproduce, the issue may be intermittent — see Step 5.
Step 2
Most checkout failures are actually payment declines. Check Stripe/PayPal dashboard for the decline reason BEFORE assuming WooCommerce is broken.
In Stripe Dashboard → Payments → Failed. Find the failed attempt. The "Decline reason" column shows codes like: card_declined, insufficient_funds, fraud_suspected, do_not_honor, expired_card.
For card_declined / do_not_honor: this is the issuing bank — not Woo, not Stripe. Customer needs to contact their bank or use a different card. Tell them so.
For fraud_suspected: Stripe Radar flagged the order. Review the Radar rule in Stripe → Radar → Rules. Adjust thresholds if you are over-blocking legitimate orders.
For 3D Secure failures (authentication_required, authentication_failed): the customer abandoned the 3DS challenge or their bank rejected it. Common on EU cards. Verify the Stripe plugin is configured to handle 3DS automatically — the modern Stripe Payment Element does this natively.
For PayPal: PayPal Dashboard → Reports → Transaction logs. Failed transactions show reason codes. Common: "BUYER_RESTRICTED" (PayPal buyer account flagged), "INSTRUMENT_DECLINED" (linked bank account declined).
If payment side shows "Succeeded" but Woo shows "Failed" — the issue is in WooCommerce, not the processor. Move to Step 3.
Step 3
Payment succeeded but order stuck in "Pending payment" = webhook failure. The processor sent the success event but WooCommerce never received it.
In Stripe Dashboard → Developers → Webhooks → your endpoint. Look at "Recent attempts." Failed webhook deliveries show HTTP error codes (403, 404, 500, timeout).
403 Forbidden: a security plugin (Wordfence, iThemes) is blocking the POST. Whitelist /wc-api/ and Stripe IP ranges (https://stripe.com/docs/ips).
404 Not Found: the webhook URL is pointing at the wrong site (often happens after a domain migration). Re-create the webhook in Stripe to use the correct URL: https://yourdomain.com/wc-api/wc_stripe/
500 Internal Server Error: PHP error during webhook processing. Check WooCommerce → Status → Logs → stripe.log for stack traces.
Timeout: server too slow to respond. Stripe expects under 5s; if your TTFB is high (over 3s), webhook processing may not complete in time. Speed up the site (see Speed Optimization tutorial).
For PayPal: PayPal Developer Dashboard → My Apps & Credentials → your live app → Webhooks → Recent deliveries. Same diagnosis pattern.
After fixing, in Stripe → click "Resend" on a failed webhook event to confirm it now succeeds. Or trigger a new test event from Stripe → Webhooks → Send test webhook.
Step 4
If error is not payment-side and not webhook, it is a plugin conflict. Systematically deactivate plugins until checkout works, then bisect.
In a staging environment (Cloudways/Kinsta have one-click staging) — NEVER live — deactivate all plugins EXCEPT WooCommerce and your active payment gateway (Stripe, PayPal Payments).
Test checkout. If it works: a deactivated plugin is the culprit.
Re-activate plugins in groups of 5. Test after each group. The group that breaks checkout contains the offender.
Within that group, bisect: activate half, test. Activate the other half, test. Narrow to the single offender.
Common offenders: (1) security plugins blocking POST requests. (2) cache plugins caching cart/checkout pages (see #1 in Common Mistakes). (3) old payment plugins still active alongside new ones. (4) shipping plugins making slow external API calls that time out checkout.
Replace or remove the offender. If the plugin is essential, contact the plugin author — they may have a known fix.
Step 5
A theme can override WooCommerce templates. Outdated overrides break on Woo updates. Switch to Storefront temporarily to test.
In your theme folder: /wp-content/themes/your-theme/woocommerce/ — any files here override WooCommerce defaults.
Compare the override files to current Woo defaults at /wp-content/plugins/woocommerce/templates/. If your theme override is on an outdated template version, it breaks features added in newer Woo releases.
Update the override to match the new Woo template (preserving your custom edits). Theme authors should do this in their next theme update — if they have not for 6+ months, the theme is abandoned.
Temporary diagnosis: switch to the Storefront theme (the WooCommerce-by-Automattic free theme). Test checkout. If it works on Storefront, your theme is the problem.
For Blocks Checkout: theme overrides do not apply the same way (Blocks is React-rendered). But page builders (Elementor, Divi) may inject their own checkout layout that breaks. See Step 6.
Step 6
WooCommerce 8.3+ defaults to Blocks Checkout. Some legacy plugins force a fallback to Classic. Mismatched configs break checkout.
WordPress Admin → Pages → Checkout. Open the editor.
Blocks Checkout: the page contains a "Checkout" block (with sub-blocks for Contact, Shipping, Payment).
Classic Checkout: the page contains the [woocommerce_checkout] shortcode.
If the page has BOTH (a Block AND a shortcode), it will render twice or break entirely. Pick one.
For new stores: use Blocks Checkout. Faster, better UX, future-proof.
For stores with old plugins that demand Classic: stay on Classic until you can upgrade or replace those plugins.
Critical: if you migrated from Classic to Blocks recently and checkout broke, the most common cause is a legacy plugin that injected JS into the Classic checkout and now does nothing. Audit and remove.
Step 7
For intermittent or hard-to-reproduce errors, enable WP_DEBUG to log every PHP warning + error during checkout. Disable after diagnosis.
In wp-config.php, find: define( "WP_DEBUG", false ); Change to true. Also add: define( "WP_DEBUG_LOG", true ); define( "WP_DEBUG_DISPLAY", false );
This writes errors to /wp-content/debug.log without showing them on the live site.
Have a friend (or you, with a fresh browser) attempt the failing checkout flow.
Open /wp-content/debug.log. Filter for entries during the checkout time window. Look for PHP errors related to WooCommerce, your payment plugin, or any third-party plugin.
Common findings: "Fatal error: Cannot redeclare function" (plugin conflict), "Allowed memory size exhausted" (PHP memory too low — increase to 512M in wp-config.php), "Uncaught exception: Stripe\Exception" (Stripe API issue, check the message).
CRITICAL: turn WP_DEBUG back to false when done. Live with debug logging on can leak sensitive data and slow the site.
Common mistakes
Caching the checkout page
What goes wrong: Customers see other customers' carts at checkout. Some get a completely empty cart and a 'no items' error. Some get the wrong shipping address pre-filled. Personal data leaks between sessions.
How to avoid: Cache plugin → exclude /checkout/, /cart/, /my-account/, and any URL with WooCommerce session parameters. WP Rocket does this automatically; manual plugins need explicit config.
Webhook URL pointing at the wrong domain
What goes wrong: After a domain migration, Stripe webhook still points at the OLD domain. Every order succeeds in Stripe but stuck in "Pending payment" in WooCommerce. You ship nothing, customers complain, money is in Stripe limbo.
How to avoid: Stripe Dashboard → Webhooks → delete the old endpoint. Re-create from inside the Stripe plugin (OAuth re-connect auto-creates the new webhook with the current domain).
Security plugin blocking /wc-api/
What goes wrong: Wordfence or iThemes blocks POST requests to /wc-api/wc_stripe/. Stripe sees 403, retries 3 times, gives up. WooCommerce never updates order status. Orders stuck forever.
How to avoid: Security plugin → firewall → allowlist /wc-api/. Also allowlist Stripe IP ranges (https://stripe.com/docs/ips) and PayPal IPs.
Theme override on outdated WooCommerce templates
What goes wrong: Theme has /your-theme/woocommerce/checkout/form-checkout.php from WooCommerce 6.0. WooCommerce is now 9.x with new fields/hooks. The theme override silently strips new features. Customers cannot complete checkout in some flows.
How to avoid: Update theme overrides to match current Woo template versions. If your theme has not been updated in 12+ months, switch themes — the author is not maintaining it.
PHP memory limit too low
What goes wrong: Checkout with 8+ cart items triggers 'Allowed memory size exhausted.' Customer sees a white screen. Order is half-created. Refund + manual order recreation required.
How to avoid: In wp-config.php: define( "WP_MEMORY_LIMIT", "512M" ); In php.ini (or via host control panel): memory_limit = 512M. Most managed Woo hosts do this by default; shared hosts often do not.
Action Scheduler stuck
What goes wrong: Background jobs (email sends, webhook processing, status updates) queue up but never run. Orders stay in "Processing" forever, customer emails do not send, no notifications fire. You have no idea anything is wrong until customers complain.
How to avoid: WooCommerce → Status → Scheduled Actions. Any pending actions older than 1 hour indicate a cron issue. Verify WP cron is running (or use system cron via wp-cron.php every 5 min). Clear stuck actions if necessary.
Recap
Done — what's next
How to set up WooCommerce payment gateways (Stripe, PayPal, Apple Pay)
Read the next tutorial
Hand it off
Checkout bugs are the highest-cost bugs in ecommerce — each failure is a lost order plus a complaint. A vetted WooCommerce specialist will systematically diagnose + fix in 1-2 sessions, typically $150-400 at $14-16/hr. Ongoing checkout monitoring runs $200-400/mo.
See specialist rates
Almost always a webhook failure. The payment processor (Stripe/PayPal) sent the success event but WooCommerce never received it. Check Stripe Dashboard → Webhooks → Recent attempts for failed deliveries. Common cause: security plugin blocking /wc-api/, or webhook URL pointing at old domain.
Ask for: device, browser, exact error message, time. Then check (a) WooCommerce → Status → Logs for any errors at that time, (b) Stripe/PayPal for the failed payment attempt, (c) Action Scheduler for stuck jobs. Often the failure is intermittent due to slow webhook or memory limit hit only on specific cart sizes.
Only as a diagnostic. Replace the Checkout block with the [woocommerce_checkout] shortcode temporarily. If Classic works and Blocks does not, you have a plugin or theme incompatibility with Blocks. Either upgrade those plugins/theme or stay on Classic — but Classic is on a deprecation path, so plan to fix the root cause.
Systematic isolation: in a staging environment, deactivate all plugins except WooCommerce + payment gateway. Test checkout. If it works, reactivate plugins in groups of 5. The group that breaks checkout contains the culprit. Bisect from there.
Three usual causes: (1) JS error specific to iOS Safari or Android Chrome — check browser console on the real device. (2) Theme media query hiding the submit button on small screens. (3) Mobile-specific plugin (mobile menu, mobile cart drawer) conflicting with Woo checkout. Test on Storefront theme to isolate.
Action Scheduler is WooCommerce's background job runner. It handles webhook processing, email sending, scheduled hooks. WooCommerce → Status → Scheduled Actions shows pending/in-progress/failed jobs. Anything pending more than 1 hour indicates a cron issue — verify WordPress cron is running or set up system cron via crontab.
WooCommerce
Most Woo owners install the Stripe plugin, see 'Connected,' and assume Apple Pay just works. Then mobile CR sits at 28% because the Payment Request Button never registered the domain. Here is the full gateway setup including Blocks Checkout compatibility.
WooCommerce
Tax and shipping is where most Woo stores ship broken. Wrong sales-tax rates trigger audits; wrong shipping rates cost 5-15% of completed checkouts. This walks the full configuration including automated tax via WooCommerce Tax and zone-based rate tables.
WooCommerce
Most WooCommerce stores load at PageSpeed 25-45 on mobile. Each second of load time costs roughly 7% of conversions. This is the systematic checklist a Woo speed specialist runs — plugin audit, caching, HPOS migration, asset optimization, and database cleanup.
WooCommerce
DIY WooCommerce is a great idea — until your plugin count crosses 30, your checkout breaks intermittently, and PageSpeed sits at 35. This is the honest framework: when the cost of self-managing exceeds the cost of hiring, and how to tell which side you're on.
WordPress
A hacked WordPress site does more than lose data — it tanks Google rankings, gets your domain flagged in Meta Ads, and loses paid traffic to spammy redirects. This is the security baseline that protects your marketing investment.