- The brief was the vaguest in ecommerce: some customers cannot complete their orders, most can, nobody knows why. No errors, no pattern, no reproduction steps.
- Two separate causes, one symptom. An ordinary usability defect that had been quietly killing orders for months, and a payment skimmer running on the checkout page and nowhere else.
- An automated scan would not have caught the injection. Everything a scanner checks, it checks on the page. This only revealed itself in behaviour.
- The method was a comparison, not a clever tool: load the real checkout as a guest shopper twice under deliberately different conditions, and see what differs.
- The second finding needed no attacker at all. Press Place Order with an incomplete card form and nothing happens: no message, no request, no trace anywhere.
- Checkout is where UX, payments, security and configuration overlap, and when any of them fails the customer-visible symptom is identical.
A retailer came to us with one of the vaguest briefs in ecommerce: some customers can't complete their orders, most can, nobody knows why. No error messages, no pattern anyone could see, no reproduction steps. Two days of testing the live public checkout turned up two separate causes. One was an ordinary usability defect that had been quietly killing orders for months. The other was a payment skimmer, injected into the store's own content management system, running in shoppers' browsers on the checkout page and nowhere else.
This is a write-up of how a symptom that reads like a conversion problem turned out to be a security incident, and what any store owner can check on their own checkout this afternoon.
Why "some customers can't check out" is the hardest ticket in ecommerce
Intermittent checkout failures resist every normal diagnostic. Support can't reproduce them. Analytics shows drop-off but not cause. The customers who hit the problem mostly don't report it: there's no error message to screenshot, so they click the button a few times, decide the site is broken, and leave. The ones who complain loudly enough to open a ticket are a small fraction of the ones affected.
That's why the symptom gets misfiled. It arrives at the marketing team as a conversion problem, and it gets treated with conversion tooling: heatmaps, session recordings, A/B tests on the button. Those tools describe the behaviour accurately and explain nothing about the cause.
What an automated scan would have said
Worth stating plainly, because it's the part most store owners get wrong about tooling: an automated site scan would not have caught the injection.
| What a scanner checks | Why the injection passed |
|---|---|
| Is the site served over HTTPS with a valid certificate? | Yes. It was. |
| Are scripts loading from suspicious third-party domains? | No. The malicious code was served from the store's own domain, inside the store's own HTML, because it had been written into the store's database. |
| Does the Content-Security-Policy flag a violation? | No. The exfiltration channel used a browser capability that CSP does not govern at all. |
| Does the page contain known-bad code signatures? | No. The payload was encoded, and key identifiers were split into fragments and reassembled at runtime specifically to defeat pattern matching. |
| Does the checkout flow work? | A scanner can't answer this. It cannot add an item to a cart, fill in an address form, and press Place Order. |
None of that is a knock on scanners. They're excellent at what they cover, page-level SEO, performance, headers, structure, and we run one ourselves. It's a statement about the shape of the coverage. Everything in the list above is checked on the page. The injection only revealed itself in behaviour, and only on one page, and only sometimes.
What manual testing actually did
The method that produced the finding is unglamorous: load the real checkout as a real guest shopper, twice, under two deliberately different conditions, and compare.
In the first session, we blocked the suspicious loader from reaching the network before the page loaded. That gave us a clean baseline, the checkout as it behaves with no interference. In the second, we let it run.
The comparison did the work. In the baseline session, the store's payment form was the gateway's own isolated frame, visible and sized normally. In the second session, that frame was still in the page but forcibly hidden, and a second card form, not part of the store's code, served from the store's own domain, had appeared in its place, showing the same fields.
That difference is the whole finding. A card form served from your own domain is readable by any script on your page. A card form inside the payment gateway's frame is not, and that isolation is the entire reason gateway-hosted card entry is considered safe in the first place. Substituting one for the other removes the protection while looking, to the shopper, completely normal.
It also explained the intermittency. The substitution only completed when the attacker's server answered within a short window. Corporate networks, VPNs, privacy extensions and hardened browsers all blocked that quietly, so those shoppers checked out normally. Everyone else got a payment form that couldn't submit, because the store's payment component was still pointed at the original frame the customer never typed into. Some customers can't place orders. Most can. Exactly as described in the brief.
The second finding, which had nothing to do with the attacker
The same testing turned up an entirely separate defect, and in day-to-day revenue terms it was probably the more expensive of the two.
Fill in every address field correctly, leave the card fields empty, click Place Order. What happens:
| Signal | Result |
|---|---|
| Visible field errors | 0 |
| On-screen messages | 0 |
| Loading spinner | Never appears |
| Network requests | 0. No attempt is ever made |
| Button | Still there, still enabled |
Zero network requests is the measurement that matters. The click is abandoned before anything is submitted, and the customer is told nothing at all. From their side, the button is simply dead. There's no error to report, nothing for support to search for, and no trace in any error-monitoring tool, which is precisely why it had survived so long.
This one needed no attacker, no sophistication and no bad luck. It just needed nobody to have clicked Place Order with an incomplete form on the live site.
What you can check on your own checkout today
None of these require a specialist. All of them are worth doing on a live storefront rather than staging.
- Press Place Order with a deliberately incomplete card form. You should get a visible, specific message within a second or two. Silence is a defect, regardless of cause.
- Press it with an incomplete address form too. Errors should appear and the page should scroll to them. If your submit button sits in a sidebar and the errors render in another column, a customer may see nothing change at all.
- Confirm your card fields live inside your gateway's frame. Your payment provider's documentation will tell you what to expect. Card inputs served directly from your own domain, when your integration says they shouldn't be, is a red flag worth escalating the same day.
- Review every CMS block, widget and "miscellaneous HTML" field in your admin for content you don't recognise. Injections favour these because they're rendered into pages by your own templates and rarely reviewed.
- Check your admin activity log for edits to those blocks: who changed what, and when.
- Check whether your Content-Security-Policy is enforced or report-only. Report-only policies produce reassuring dashboards and block nothing.
- Look at what your checkout page logs in the browser console. Broken address autocomplete, failed template loads and null API keys all show up here, and any of them can be costing you orders on their own.
Why this ended up in a conversion audit at all
The brief we were given was a revenue question. The answer turned out to be a security incident with regulatory obligations attached, sitting underneath an ordinary UX defect, both producing the same customer-visible symptom.
That's not unusual. Checkout is the one page where UX, payments, security and platform configuration all overlap, and the symptom they produce when any of them fails is identical: the order doesn't complete and nobody knows why. Which is the argument for having someone look at the actual flow, from the outside, as a customer, on a schedule, rather than only when something has already gone visibly wrong.
A manual audit puts a human behind the flow: real guest checkout testing, payment behaviour, UX, and the configuration issues automation can't see. Not ready for that? Run the free automated site audit for the page-level picture of design, SEO, performance and security issues, with revenue impact estimated per finding. It covers a different shape of problem, as the table above makes clear, and it is a reasonable place to start.
This account is published with the client's permission and anonymised at their request. All testing was carried out on the live public storefront as an ordinary guest shopper. No card number was entered, no order was placed, and no administrative or authenticated access was used. Store, domain, payment provider and technical indicators are withheld and remain in the client report. Last reviewed October 2026.