Direct answer
Let WooCommerce's payment-complete flow decide, and answer only that one question. When a gateway marks an order paid, WooCommerce asks which status the order should receive. Check that exact order's line items once, in memory. If every item resolves to a native virtual product, return Completed. For any mixed, physical, unpaid, or uncertain order, return the incoming status unchanged. Never scan old orders, never run a background job, and verify the rule with one paid test order.
Auto Complete Virtual Orders for WooCommerce follows that filter-only boundary. Free adds one global switch for all-virtual orders. Pro adds a virtual-plus-downloadable matching choice, up to twenty-five product or category exclusions, and one order note naming the rule.
Why paid virtual orders sit in Processing
A status is a workflow decision, not a label. WooCommerce's official order status guide explains that Processing means the order still needs handling, while Completed means it needs none. Under current core rules, a paid order arrives as Completed automatically only when nothing in it needs processing. A virtual product with no download file still counts as needing handling, and a gateway that reports payment late or never leaves the order where it started.
The manual fix is clicking every paid order from Processing to Completed by hand. The broad fix is an automation suite. The narrow fix is answering the one question the payment-complete flow already asks.
| Approach | What it touches | Main failure surface |
|---|---|---|
| One native filter on the paying order | The status returned for that exact payment | Correct product identity for the order's own items |
| Broad order-automation suite | Modes, gateway rules, schedules, bulk actions | Wrong-order transitions and retroactive scans |
| Manual completion | Every paid order, every day | Human delay and missed orders |
Public support reports show the pain. One merchant reported PayPal-paid virtual product orders never moving to Completed and staying in Processing. A membership seller reported all-virtual, downloadable orders randomly stuck in Processing with the expected product settings ticked. A separate topic reports every virtual product order staying in Processing until each is completed by hand. These reports document individual incidents only. They do not establish that every store hits the same failure.
Let WooCommerce own the transition
WooCommerce decides when a payment completes and which orders exist. A helper should only answer the status question for the order being paid right now. That rule prevents several dangerous shortcuts:
- Do not scan or bulk-update existing orders.
- Do not touch unpaid orders, manual-gateway orders awaiting payment, refunds, or subscriptions.
- Do not add gateway-specific rules or payment-capture logic.
- Do not register a custom status or force a transition outside the payment-complete flow.
- Do not run cron, Action Scheduler, queues, or any background job.
- Do not write stock, product, or customer changes; the native Completed mail stays WooCommerce's own.
WooCommerce's current abstract-wc-order.php source is the authoritative reference for payment completion. The lesson is ownership: WooCommerce owns order behavior; a narrow plugin can own only the answer to one filter.
Answer the filter in one pass
The safe algorithm is short:
- Confirm the switch is on and the incoming value is a valid order object.
- Loop the paying order's own line items once.
- Resolve each item to its product and confirm it is a native virtual product.
- If every item resolves, return Completed.
- Otherwise return the incoming status untouched.
Pro adds one matching choice and one bounded exclusion list. An excluded product or category anywhere in the order leaves the whole order native, and Pro writes at most one order note naming the rule that completed it. The virtual-plus-downloadable choice tightens matching for stores that distinguish the two product flags; the official virtual and downloadable products guide explains how those flags differ.
Keep email and delivery out of scope
Completing an order already triggers WooCommerce's native Completed mail and any download delivery the platform owns. A narrow filter must not add its own mail, license delivery, or download changes on top. One transition, one native consequence, nothing extra.
Turn it on and prove one order
- Open WooCommerce > Auto Complete Virtual Orders.
- Enable Auto complete paid virtual orders and save.
- Place one gateway-paid test order containing only a virtual product.
- Watch the order arrive as Completed without a manual click.
- Place a second order mixing one virtual and one physical product and confirm it keeps its native status.
- Confirm older Processing orders are unchanged; this design never touches them.
- Disable the switch and confirm the next paid order follows native behavior again.
The correct pass condition is selective output. A tool that completes every paid order, or rewrites the past, is solving a different and more dangerous job.
Diagnose a missing completion
| Symptom | Check first | Safe expected result |
|---|---|---|
| Test order stayed Processing | Virtual flag on the product, gateway payment signal | All-virtual paid order arrives Completed |
| Mixed order stayed Processing | Working as designed | Any physical item leaves the order native |
| Manual-gateway order unchanged | Whether the gateway ever fires payment complete | Native behavior preserved |
| Older orders unchanged | By design; no retroactive scans | History stays exactly as WooCommerce wrote it |
| Pro exclusion skipped an order | Exclusion list contents | One excluded item leaves the whole order native |
| No order note | Pro exclusions and rule match | At most one note, only on an order the rule completed |
| Completed mail missing | WooCommerce email settings | The plugin sends no mail of its own |
| Completed mail arrived twice | Other mail or automation plugins | Only the native Completed mail runs |
| Switch off but orders still complete | Whether another plugin filters the same hook | Deactivation removes this filter's answer |
| Download links missing | Product download settings in WooCommerce | Delivery stays WooCommerce's own job |
Start with the product's Virtual checkbox and the gateway's payment signal. A gateway that marks orders paid only after review, or never, gives the filter nothing to answer.
Test the negative space
The most important checks prove what did not change:
- Unpaid, manual-gateway, mixed, physical, empty, and malformed orders keep their incoming status.
- No order query, scan, or bulk write occurs outside the single payment-complete call.
- Duplicate filter calls produce the same bounded answer.
- WooCommerce missing causes no fatal error and registers nothing.
- Pro or the license API unavailable leaves the Free switch working.
- Update, downgrade, rollback, and uninstall preserve every order and product.
- Deactivation removes the filter immediately and restores native Processing behavior.
Release acceptance for this product should run those drills on a real WordPress and WooCommerce installation before any store trusts it. A packaged check is evidence about the ZIP, not proof that every gateway signals payment the same way.
Keep Pro bounded
Pro changes only which orders match, never how completion works. The exclusion list holds at most twenty-five products or categories, the matching choice has two values, and the order note names the rule and stops at one. If Pro or its license check is unavailable, the valid Free switch keeps working with no fatal error and no public remote request.
Resource boundary
A status answer needs one autoloaded option read and one in-memory loop over the paying order's own items, inside the payment-complete call itself. It needs no cron event, custom table, order query, remote request, JavaScript, stylesheet, or idle work. Repeat any performance benchmark on the real hosting stack before a high-traffic launch.
Common mistakes
Completing old orders in bulk
Retroactive scans rewrite history the merchant never audited. Complete only the order being paid now; fix the past by hand if it matters.
Guessing virtual from price or product type labels
Free products and oddly configured items break guesses. Resolve each line item to its native product record and read the real flag.
Adding gateway rules
A PayPal-only rule misses the boundary. The filter runs after the gateway reports payment, so one answer covers every gateway that signals correctly.
Expecting the plugin to fix a broken gateway
If the gateway never reports payment, no status filter can help. Fix the gateway signal first; the filter only answers orders that arrive paid.
Treating Processing as an error state
Processing is often correct. Mixed and physical orders should keep it.
Market boundary
The official WooCommerce Marketplace lists a current Order Status Control offer at $29 per year, and WordPress.org has established autocomplete plugins. That proves a paid category exists. It does not prove demand or revenue for a narrower filter-only product. StoreFixKit Pro is $49 per year for a single site; the commercial hypothesis is that some digital sellers will pay for one safe switch instead of an automation suite. Measure StoreFixKit installs, completed setups, paid conversions, refunds, and support load rather than inheriting a competitor's numbers.
FAQ
Why do my paid virtual orders stay in Processing?
Under current core rules, an order completes itself only when nothing in it needs processing. A virtual product without a download file still counts, and some gateways report payment late or never.
Will this complete my existing Processing orders?
No. The filter answers only for the order being paid at that moment. Older orders stay exactly as they are unless you change them by hand.
What happens to an order with both virtual and physical items?
It keeps the native status WooCommerce chose. The rule completes only orders whose every line item is a virtual product.
Focused plugin
Use the one-job implementation.
Move a gateway-paid order to Completed through WooCommerce's own payment-complete decision when every line item is a native virtual product, without scans, cron, bulk updates, or changes to any other order.