Guide · Fake payment form

A card form that is not your gateway’s is collecting your customers’ cards.

A customer says checkout asked for their card twice. Another says the first attempt failed with an error and the second one worked. Or you noticed the card fields on your checkout look slightly different from the gateway’s usual ones. These are the signs of an injected payment form: JavaScript that draws a convincing card form in front of — or instead of — your real gateway, takes what the customer types, and then gets out of the way so the order still completes. This guide covers how to confirm it from a browser, where the script usually lives, and what to do in what order.

Symptom
Card details asked for twice, or a first attempt that always “fails”
Mechanism
Injected JavaScript drawing a lookalike card form
Why it works
Hosted gateway fields protect only what is typed inside them
Where it lives
Database content, theme files, a fake plugin, or a tag manager

How it works

The attacker does not need to break the gateway — only the page around it

Most WooCommerce stores no longer collect card numbers on their own page. Stripe, PayPal and similar gateways draw their card fields inside an iframe served from the gateway’s domain, or send the customer to a hosted page, so the store’s code never touches the number. That is a real protection, and it is the reason this attack looks the way it does.

An injected payment form does not try to read the gateway’s iframe, which the browser would not allow. It draws its own form on the store’s page instead — styled to match the gateway’s, sometimes with the gateway’s logo — in the place where the real fields would appear. The customer types card number, expiry and security code into the attacker’s form. A script sends those values to a domain the store has nothing to do with, then shows an error such as “payment failed, please try again”, removes the fake form, and lets the genuine gateway appear.

The customer re-enters the card, the real payment succeeds, and the order completes. Conversion, refund and error figures stay normal, which is why the first sign is usually a customer, a bank or the payment provider rather than anything in the WooCommerce dashboard. Many variants also run once per browser and stay quiet for logged-in administrators, so the store owner’s own test checkout can look perfectly normal.

Figure 1 · One checkout, two card forms

The fake form takes the card first, then steps aside for the real one.

What the customer sees

  1. A card form at checkoutLooks like the gateway’s
  2. “Payment failed, try again”After the first attempt
  3. A second card formThe genuine gateway
  4. Order confirmedPayment settles normally

What the injected script does

  1. Draws a lookalike formIn place of the gateway fields
  2. Sends the card detailsTo a domain the store never uses
  3. Removes itselfSets a flag so it runs once
  4. Reveals the real gatewayNothing to report
An injected overlay as it typically runs: the customer’s first attempt goes to the attacker, the second to the real gateway, and the order completes, so nothing in the store’s reports looks wrong. Illustrative reconstruction.

Confirm it from a browser

How to tell the fake form from the real one

Use a private window or a clean browser profile, logged out, ideally from a network that is not the office’s, and on a phone as well as a desktop. Use a test card number or leave the fields empty.

  1. 01

    Check whose page the card fields belong to

    Right-click inside the card number field and choose Inspect. Genuine hosted fields sit inside an iframe whose source is the gateway’s own domain. A card input that is an ordinary field in the store’s page, with no iframe around it, is not the gateway’s — whatever its logo says.

  2. 02

    Watch the network while the form is used

    Open the browser’s developer tools on the Network tab before the checkout loads, and keep “Preserve log” switched on. Requests from the checkout to a domain you cannot attribute to your theme, plugins, analytics or gateway are the lead — especially one fired the moment card fields are filled or the button is pressed, often as an image, beacon or fetch request carrying encoded data.

  3. 03

    Compare the delivered source with the rendered page

    View the page source (what the server sent) and the Elements panel (what the browser built from it). A card form present in Elements but not in the source was added by a script; search the Sources panel or the source for the form’s field names to find which file added it.

  4. 04

    List every script the checkout loads

    In the Network tab, filter by JS and note each file and domain on the checkout page. Each one should belong to something you can name. Pay attention to scripts that load only on the checkout or the order-pay page, and to lookalike names of real services.

  5. 05

    Repeat on another device and connection

    Many injected forms check for a logged-in cookie, a desktop browser, or a previous visit before they appear. One clean test does not clear the site; several conditions do.

Where it hides

Four places the injected script usually lives

The script has to be delivered on the checkout page, so it lives somewhere that page draws from. Preserve a full backup of files and database before changing anything, and search the copy where you can.

The database

Script tags stored in options, widgets, posts or plugin settings that print on every page or only on checkout. Searching the options table for script tags is a read-only first look; the table prefix may not be wp_ on your site.

WP-CLI
wp db query "SELECT option_name, LENGTH(option_value) FROM wp_options WHERE option_value LIKE '%<script%'"

Many legitimate options contain markup. Look at the names you cannot place, and read the value before judging it.

Theme files

A line added to functions.php, a checkout template override, or a minified JavaScript file in the theme that no update explains. Encoded strings and indirect execution are the tell:

shell
grep -rnE '(atob|fromCharCode|eval)\(' wp-content/themes --include='*.js' --include='*.php'

Minified libraries trigger this too; it narrows the list rather than proving anything.

A plugin that is not what it says

A plugin with a plausible name that nobody installed, often a single small file, or a real plugin’s folder with one extra file in it.

WP-CLI
wp plugin list --fields=name,status,version

Compare the list with what the store actually uses; a fake plugin is covered in its own guide.

A tag manager or third-party script

A Custom HTML tag in a Google Tag Manager container, or a compromised third-party script the checkout already loads. Neither shows up in a file or database search. Review the container’s version history for tags nobody can account for, and who has publish rights to it.

Respond

The order that keeps evidence and limits exposure

A skimmer is an incident with obligations, not a file to delete. The order below keeps what you will need to establish when it started and what it took.

  1. 01

    Preserve before you remove

    A full copy of the files and the database, the web server access logs, the exact script you found and the domain it sends to, and screenshots or a HAR export of the network traffic from your test. Note dates from any customer or bank reports.

  2. 02

    Contain the checkout

    If the source is not yet found, take the checkout offline or switch to a hosted-page payment flow that sends customers off-site, rather than leave the form running while you search.

  3. 03

    Tell your payment provider

    Your gateway or acquirer sees fraud from the other side, may already have a pattern for your store, and will tell you what they need. Rotate the gateway API keys and webhook secrets stored in WooCommerce as part of the cleanup.

  4. 04

    Remove the injection and its persistence

    Delete the script wherever it lives, then look for whatever put it there: an administrator you do not recognise, a loader in the database, a modified plugin or theme, a scheduled task. A form that comes back was not the only thing planted.

  5. 05

    Rotate access

    WordPress administrators and application passwords, hosting, SFTP and database credentials, and access to any tag manager or CDN account — from a device that is not part of the incident.

  6. 06

    Verify from the visitor’s side

    Repeat the browser checks above over several days, on several devices and connections, and confirm the checkout loads only scripts you can attribute.

  7. 07

    Take advice on notification

    Card data exposure can create duties towards customers, card schemes and, in some places, a regulator. They depend on where the store and its customers are and on the store’s agreements with its payment provider. This guide is engineering guidance, not legal advice: establish the exposure window first, then take proper advice on what follows from it.

Prevent the next one

What makes an injected form harder to plant and quicker to spot

Fewer people and plugins that can change the checkout

Every administrator account and every plugin with code on the checkout page is a way in. Remove what the store does not use and keep the rest updated.

Know what the checkout loads

Keep a list of the domains your checkout legitimately loads scripts from. A Content-Security-Policy that allows only those turns a planted script from another domain into a blocked request and a browser report.

File and database change monitoring

Alerts on changes to theme files, plugin files and the options that print markup catch an injection in hours instead of when the first customer complains.

Treat customer reports as data

One customer saying the card was asked for twice is worth a check from a clean browser that day.

Common questions

Common questions about fake payment forms

We use Stripe or PayPal with hosted fields. How can card numbers be stolen?

Hosted fields protect what is typed inside the gateway’s iframe, and the store never sees it. An injected form does not touch that iframe: it draws a separate form on the store’s own page and asks the customer to type the card there first. The protection is real; the attack goes around it.

Our own test checkout looked normal. Does that clear the store?

Not on its own. Injected forms commonly hide from logged-in administrators, show once per browser, or appear only on mobile or for visitors from certain places. Test logged out, in a private window, on more than one device and connection, and check the network traffic rather than only how the page looks.

Will a malware scanner find it?

Sometimes. A script stored in the database, loaded from a tag manager, or fetched from another domain at run time may never appear as a suspicious file. A browser-side check of what the checkout actually loads is the more reliable test.

Should we tell customers?

If card details were exposed, notification duties may apply to customers, your payment provider and, in some places, a regulator. Establish when the form went live and what it collected, involve your payment provider, and take proper advice on the obligations before you announce anything.

We removed the script and it came back. Why?

Because something else on the site put it there. Look for a loader in the database, a modified theme or plugin file, an administrator or application password you do not recognise, and a scheduled task, and rotate every credential before calling it clean.