woocommerce Case study
Recover MU Plugin
Two things were leaking revenue: carts abandoned before checkout, and orders that reached the gateway and stopped. This plugin handles both, and along the way it fixes a WooCommerce behaviour that was leaving unpaid orders stuck in pending indefinitely.
The business problem
Existing recovery plugins wanted to own the customer record, add their own tracking and send on schedules that ignore how this store actually operates. Unpaid orders were a second, separate problem. WooCommerce's own cancellation of unpaid orders did not fire reliably here, so pending orders piled up and both the reporting and the stock position drifted.
What I delivered
- a2-recover-sms-mu.php, a 1,295-line MU plugin scheduling recovery messages through Action Scheduler in its own group rather than on wp-cron.
- Recovery triggers at four distinct points: order processed at checkout, order entering pending, order entering on-hold, and add-to-cart or checkout-view for carts that never became orders.
- A pending-to-failed enforcement path with a 31-minute timeout, which is deliberately just past the gateway's own window.
- A five-minute sweep with a lock, plus a watchdog on wp_loaded, so the transition still happens on a site where scheduled events are unreliable.
- An interception of WooCommerce's own unpaid-order cancellation, so the two mechanisms cannot both act on the same order.
- Short links that resolve to specific category or gift-card destinations, so a recovery message can point at something more useful than the cart.
- Coupon application from a cookie at three separate cart and checkout hooks, so an incentive attached to a recovery message survives the customer's route back.
Technical approach
- I used Action Scheduler rather than wp-cron, in a dedicated group. A recovery message must survive a missed cron run, and I have to be able to look it up when a customer asks why they received one.
- I set the 31-minute timeout one minute past the gateway window on purpose, so the plugin never races the payment provider for the same order.
- The sweep takes a lock before it runs. Two overlapping sweeps on a slow site is exactly how a customer ends up receiving the same message twice.
- I added the wp_loaded watchdog because scheduled events on a store behind aggressive caching are unreliable. The sweep is the primary path; the watchdog only matters when the environment misbehaves.
- The plugin reads the SMS template name and API key from constants defined elsewhere, so no credential lives in this file.
Result and evidence
Recovery messaging runs on the store, and pending orders now transition to failed on a predictable timetable instead of accumulating. I do not have an isolated recovered-revenue figure. The coupon and the message went live together, and I cannot separate the two honestly. The result I can state is operational: the pending backlog stopped growing.
Commercial value
Both halves pay for themselves in ordinary operation: recovered carts on one side, accurate order status on the other. Stock levels and daily reporting both depend on that status being right.
Readable implementation brief
implementation_brief {
project: "Recover MU Plugin"
file: "mu-plugins/a2-recover-sms-mu.php (1295 lines, v0.2.0)"
queue: "Action Scheduler, dedicated group, not wp-cron"
triggers: "checkout processed, order pending, order on-hold,
add-to-cart, checkout viewed"
pending_rule: "pending -> failed after 31 minutes
(one minute past the gateway window)"
reliability: "5-minute locked sweep + wp_loaded watchdog"
conflict: "intercepts Woo's own unpaid-order cancellation"
return_path: "short links to category/gift-card targets,
coupon restored from cookie at 3 hooks"
secrets: "API key and template read from external constants"
}What this project shows
The part I would defend in review is the timeout choice and the lock. In recovery messaging the worst thing that can happen is messaging a real customer twice, or messaging someone who has already paid.
Building the watchdog as a backstop to the sweep, rather than as the main path, comes from the same instinct. The primary mechanism should be the correct one, and the backstop should only matter when the environment misbehaves.