The Slack message arrived mid-morning.
“Can you just add a field on checkout? One checkbox. Gift wrap. Half an hour?”
I’ve heard that sentence enough times that I should have a stock reply. I didn’t that day. The shop looked calm. Theme was stable. Orders were coming in. A boolean on the order form felt like the smallest job on the board.
I quoted it like a small job. Then I spent three days doing a cart autopsy.
What “just a field” actually touches
On a brochure site, a checkbox is a checkbox. On WooCommerce checkout, a field sits in the middle of tax, shipping, fees, payment gateways, and a stack of plugins that all listen to the same hooks.
That gift-wrap box needed to:
- Show only on certain products
- Add a fee when checked
- Keep VAT correct when the fee appeared
- Leave free-shipping rules alone when the cart tipped over a threshold
- Still let the gateway render after the totals refreshed
None of that was in the Slack message. None of it sounded like “half an hour” once I opened the existing customisations.
The previous developer had already wired a couple of checkout tweaks in a child theme. A shipping plugin recalculated rates on updated_checkout. The tax setup mixed inclusive and exclusive display depending on the customer’s country. The gateway plugin was picky about when totals were considered “final.”
One neat checkbox. Four systems that all assumed checkout never changed shape.
The quiet breakage
Staging looked fine at first. Tick the box, fee appears, untick, fee goes. Lovely.
Live was less lovely.
With the box ticked, UK VAT on the wrap fee sometimes double-counted against a product already taxed inclusive. Unticked, a shipping method that should have stayed free for orders over a threshold vanished for a subset of carts. And on mobile Safari — always mobile Safari — the card gateway failed to re-initialise after the AJAX refresh that ran when the checkbox flipped.
Customers could still reach checkout. Some could still pay. Support started getting the soft complaints: “gift wrap charged wrong,” “shipping disappeared,” “pay button just spins.”
Nothing threw a red fatal. That is what made it expensive. Soft breakage on a live till is harder to see than a white screen of death.
Three days of cart autopsy
Day one was denial dressed as debugging. I chased the fee snippet. Then the tax class. Then the shipping zone table. Each fix moved the symptom.
Day two I stopped patching symptoms and built a matrix: same three products, gift wrap on and off, UK and EU addresses, free-shipping threshold just under and just over, desktop Chrome and iPhone Safari, gateway in test mode with a real card flow. Every cell got a screenshot and a note.
Day three the pattern was obvious. The checkbox itself was innocent. The update_checkout cascade was not. The fee hook ran after tax in one path and before it in another. The shipping plugin cached a rate package that ignored the fee line on the second refresh. The gateway script bound once on page load and never rebound after the fragments replaced the payment block.
I unwound the child-theme checkout code, moved the field into a small, explicit plugin with a clear load order, forced totals to recalculate in one direction only, and re-tested the matrix until every cell was boring.
Boring is the goal. Checkout should be boring.
How I scope checkout changes now
I still take “add a field” jobs. I no longer price them as a checkbox.
Before I quote, I ask:
- What should happen to tax when the field changes the total?
- Which shipping methods must still appear with the field on and off?
- Does any gateway or express-pay button re-render after
updated_checkout? - Is there already custom checkout PHP in the child theme, Code Snippets, or an abandoned “checkout tweaks” plugin?
- Can we reproduce the happy path and the edge path on staging with a real test order both ways?
If the client only wants the field and not the matrix, I explain the risk in plain English: a till is a system, not a form builder. A half-hour visually can be a multi-day integration once tax, shipping, and payment are in the room.
I also refuse to ship a checkout change without:
- Staging that mirrors live plugins and tax settings
- A written test matrix (field on/off × key countries × shipping thresholds × gateway)
- At least one completed test order in each critical cell
- A rollback plan that does not involve “we’ll hotfix on Friday afternoon”
That is not ceremony. That is how you avoid learning the hard way that gift wrap can take down a card form.
What I tell clients when they ask for “just” a checkout tweak
I say yes to the outcome — capture gift wrap, marketing consent, delivery notes, a B2B PO field — and I scope the till around it.
I price the investigation of existing checkout customisations as part of the job, not a surprise day-two invoice. If the child theme is already a maze of woocommerce_checkout_* hooks, the quote reflects untangling that maze, not drawing a new checkbox on top.
And I say out loud that “half an hour” is a UI estimate, not an integration estimate. Most shop owners appreciate honesty more than a cheap number that explodes when VAT goes wrong on a Bank Holiday weekend.
The practical takeaway
Before you — or anyone you hire — adds “just a field” to WooCommerce checkout, treat it as a change to tax, shipping, and payment until proven otherwise.
Map what already listens to checkout updates. Test the field on and off with real totals. Watch the gateway after every AJAX refresh. Prefer a small, named plugin over another anonymous blob in functions.php.
A checkbox is small. A broken cart is not.
Need a hand?
If you want a WooCommerce developer who scopes the till before he draws the field — takeover, custom checkout, or a change that needs to stay boring under real orders — hire me. Fixed-price quote, no obligation.
Written by Connie, an AI, from Neil Matthews’ notes, archive, and direction. Neil directed and edited this piece. He did not write these words. Augmented publishing by Neil Matthews.
