Pillar guide

The checkout, in detail

The step where an abandoned session is a lost order — and the step almost no scan ever reaches. What breaks in both WooCommerce checkouts, how to walk a real basket-to-order journey with a keyboard and a screen reader, and the fix for each failure, including the ones no script can find.

Published 2026-08-04 23-minute read 29 primary sources, dated

By the end of this guide you should be able to do four things: say why the checkout is the step that decides both a conformance claim and a conversion rate; work out which of the two WooCommerce checkouts your store actually serves, and what each one gets wrong; walk a real basket-to-order journey with a keyboard and a screen reader and come out holding a written list of barriers; and fix each barrier on that list, or know honestly which ones need a person rather than a script.

WooCommerce accessibility, end to end maps the whole store. This guide stops at the basket and does not leave until an order is placed.

Why the checkout is the step that decides it

Two of WCAG’s conformance requirements matter more here than any single criterion, and both point at the till.

The first is complete processes. W3C states it plainly: “When a web page is one of a series of web pages presenting a process (i.e., a sequence of steps that need to be completed in order to accomplish an activity), all web pages in the process conform at the specified level or better.” The worked example is not a hypothetical — it is a shop. “An online store has a series of pages that are used to select and purchase products. All pages in the series from start to finish (checkout) conform in order for any page that is part of the process to conform.”

The second is full pages: “Conformance (and conformance level) is for full web page(s) only, and cannot be achieved if part of a web page is excluded.” You cannot set aside the payment fields because a gateway plugin renders them, and you cannot set aside the address block because a checkout plugin replaced it.

Together those two rules mean a store’s conformance claim is decided at its weakest step. An immaculate product page attached to a checkout a screen-reader user cannot complete does not buy a partial pass; it fails the process, and the process is the unit of measurement.

The commercial argument is sharper still. A customer blocked on a category page never chose anything. A customer blocked at checkout has already picked the item, accepted the price, typed a name and half an address, and formed the intention to pay you. That is the most expensive place in the shop to lose someone, and it is invisible in analytics: the session ends looking like ordinary cart abandonment. Nobody emails to say the postcode field had no label.

The law points the same way. Article 2(2) of Directive (EU) 2019/882 states that “this Directive applies to the following services provided to consumers after 28 June 2025”, and e-commerce services are point (f) on that list. Annex I, Section IV then sets three additional requirements for e-commerce services — two of them about the checkout itself, including “providing identification methods, electronic signatures, security and payment services which are perceivable, operable, understandable and robust”. A Directive is implemented through each Member State’s own law, so whether and how it reaches your shop is a question for a lawyer rather than for us. But the legislature’s attention was plainly on the payment step, not the home page.

Which checkout are you actually shipping

WooCommerce has two, they produce different markup, and a pass on one tells you almost nothing about the other.

The classic checkout is server-rendered. Its template outputs <form name="checkout" method="post" class="checkout woocommerce-checkout" ... aria-label="Checkout"> and builds every field through woocommerce_form_field(), so the ids, labels and attributes exist in the HTML your server sends.

The block checkout renders its form in the browser. The fields are not in the served HTML; they appear once JavaScript runs. Extra fields are registered in PHP with woocommerce_register_additional_checkout_field(), called on woocommerce_init or later.

To tell which you have, view source — the served HTML, not the inspector — and search for billing_first_name. If it is there you are on the classic checkout; if the form’s place is nearly empty, you are on the block one. That distinction matters more for testing than for fixing: a scanner reading server HTML sees the classic checkout’s problems and nothing at all on a block checkout, because the form it should audit does not exist yet.

The failures, one at a time

Labels, placeholders and the required marker

The oldest failure in the catalogue is a field whose only visible text is a placeholder. Placeholders vanish on the first keystroke, are usually below the contrast threshold, and are not a programmatic label. SC 1.3.1 requires that “Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text”; SC 3.3.2 requires that “Labels or instructions are provided when content requires user input”; SC 4.1.2 requires that a component’s “name and role can be programmatically determined”. A placeholder-only address field fails all three at once.

W3C is also explicit that this covers optional fields: “The criterion applies to all form fields, whether they’re required or optional.”

Current WooCommerce core handles the required marker correctly, which surprises people. woocommerce_form_field() adds aria-required="true" and renders the visual marker as <span class="required" aria-hidden="true">*</span> — visible to sighted users, silent to a screen reader already told the field is required. If your checkout announces a bare asterisk, or marks required fields in red and nothing else, that is your theme or a checkout plugin. See form labels.

The autocomplete tokens

SC 1.3.5 asks that “The purpose of each input field collecting information about the user can be programmatically determined” where the field serves one of the listed input purposes. The mechanism in HTML is the autocomplete attribute, which as W3C puts it “only accepts a certain number of specific well-defined fixed values” — given-name, family-name, address-line1, postal-code, tel, email and the rest.

Core does more here than it gets credit for. WooCommerce’s own default address fields already carry tokens, and it prefixes them the way the HTML specification allows, so billing first name ships as autocomplete="section-billing billing given-name", and the checkout’s email and phone fields are set the same way. What arrives with nothing is everything else: a field a shipping plugin adds, a custom field in a site plugin, an address block a page builder re-renders, or any field whose args were rebuilt by a theme that dropped the argument on the way through. Those are the ones to check, and on most stores there are several.

Adding the missing tokens is the one checkout fix that is pure gain and needs no judgement. Without them a stored address is useless and the customer retypes it — including those for whom typing an address is the hardest part of buying anything. With them, the longest form in the shop gets shorter for everyone. See autocomplete.

Errors: identified, suggested, announced, and focused

Checkout error handling is four separate criteria and most stores fail three of them.

Identified. SC 3.3.1: “If an input error is automatically detected, the item that is in error is identified and the error is described to the user in text.” A red border is not text.

Suggested. SC 3.3.3: “If an input error is automatically detected and suggestions for correction are known, then the suggestions are provided to the user, unless it would jeopardize the security or purpose of the content.” “Invalid postcode” knows nothing; “Postcode should look like SW1A 1AA” knows something.

Announced. SC 4.1.3 requires that status messages “can be presented to the user by assistive technologies without receiving focus” — and W3C’s own example is an error summary: text added to the form reading “5 errors on page”, with “The screen reader announces the same message.”

Reachable. SC 2.4.3 asks that “focusable components receive focus in an order that preserves meaning and operability”. After a failed submission the customer is at the bottom of the form on the place-order button, and the errors are at the top. If focus does not move, nothing has been communicated even when the markup is correct.

Here the WooCommerce source is worth reading rather than guessing about, because current core already does most of this. The shipped notices/error.php renders the summary as <ul class="woocommerce-error" role="alert"> and sets no tabindex. The classic checkout’s script then takes that returned markup, strips the role off the list — a role="alert" on a <ul> costs you the list semantics — sets tabindex="-1" on it, wraps it in a fresh <div role="alert">, and hands it to submit_error(), which prepends it, re-validates the fields, scrolls to the notices and focuses .woocommerce-error[tabindex="-1"]. Because the attribute was added a moment earlier in JavaScript, that focus move lands. Core also links each summary item to the field it belongs to and puts aria-invalid="true" and aria-describedby on each failing input.

Two gaps survive that, and they are the ones to test for. The technical-error fallbacks — the branches that print one generic message when the response carries no field-level messages — build their own <div class="woocommerce-error"> with no tabindex, so on those failures focus stays on the place-order button. And the whole chain depends on shipping core’s notice template and core’s checkout script: an overridden notices/error.php, a replacement checkout, or a builder that re-renders the form takes it with them. That is why the fix later in this guide is written to sit behind core rather than fight it.

Focus that vanishes or hides

Three criteria, all of which a keyboard walk finds in about ninety seconds.

SC 2.4.7 requires that “Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible” — and a :focus { outline: none } in a theme stylesheet, still common, removes it across the whole checkout.

SC 2.4.11, new in 2.2, requires that “When a user interface component receives keyboard focus, the component is not entirely hidden due to author-created content”, and names a culprit every shop has: “A notification implemented as sticky content, such as a cookie banner, will fail this success criterion if it entirely obscures a component receiving focus.” Sticky headers and sticky order summaries do the same on a two-column checkout.

And focus order again, in the shipping-method radios and the payment panels, where changing a selection re-renders a region and focus lands wherever the new DOM puts it.

Targets and taps

SC 2.5.8 asks that “the size of the target for pointer inputs is at least 24 by 24 CSS pixels”, with exceptions for spacing, an equivalent control elsewhere, inline targets, user-agent-controlled sizing, and essential presentation. The intent names who it is for: users “who have difficulty with fine motor movement find it difficult to accurately activate small targets when there are other targets that are too close.”

The offenders are predictable — the quantity stepper’s plus and minus, the icon-only remove-item control, a small “apply” beside the coupon field, the terms checkbox on mobile. The remove control usually fails twice, because it is both tiny and nameless, and a button with no accessible name announces as “button” and cannot be operated by voice control either. See empty buttons.

Contrast where it matters most

SC 1.4.3 asks for a contrast ratio of “at least 4.5:1” for text, or 3:1 for large-scale text, with exceptions for inactive components, decoration and logotypes. The checkout traps are the ones nobody designs deliberately: helper text under a field, the “(optional)” suffix, the order-summary tax and shipping lines, and — worst — the error notice itself, often a light red on white that lands well under the threshold. The message telling a struggling customer what went wrong is the last thing that should be hard to read.

SC 1.4.11 covers what is not text: “Visual information required to identify user interface components and states” needs 3:1 “against adjacent color(s)”. W3C is specific about form fields: “Where a text-input has a visual indicator to show it is an input, such as a bottom border (#767676), that indicator must meet 3:1 contrast ratio.” A fashionable checkout with hairline pale-grey borders fails that, and so does a focus ring too faint against the field it surrounds.

And SC 1.4.1: “Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.” A field turned red is colour alone. A field turned red with a text message beneath it is not. Colour contrast is a brand decision as much as a code one, which is why it is the one family we never change without a person approving it — see colour contrast.

Re-typing the address, and the login wall

Two WCAG 2.2 criteria land squarely on guest checkout.

SC 3.3.7 Redundant Entry, at Level A, requires that “Information previously entered by or provided to the user that is required to be entered again in the same process is either: auto-populated, or available for the user to select”, with exceptions where re-entry is essential, needed for security, or the earlier information is no longer valid. Its worked example is the pattern you already have: “A form on an e-commerce website allows the user to confirm that the billing address and delivery address are the same address.” A checkout that makes someone type the same address twice fails a Level A criterion on the longest form in the store.

SC 3.3.8 Accessible Authentication (Minimum) applies wherever a login sits in the checkout path: “A cognitive function test (such as remembering a password or solving a puzzle) is not required for any step in an authentication process” unless one of the listed alternatives applies — another method that is not a cognitive function test, a mechanism to assist with it, object recognition, or personal content. This is where blocking paste in the password field, or hiding account creation behind a puzzle, becomes a conformance problem rather than an annoyance.

Review and confirm before the money moves

SC 3.3.4 Error Prevention (Legal, Financial, Data) is the criterion that exists for checkouts specifically. For transactions that are financial, at least one of three things must be true: “Reversible: Submissions are reversible. Checked: Data entered by the user is checked for input errors and the user is provided an opportunity to correct them. Confirmed: A mechanism is available for reviewing, confirming, and correcting information before finalizing the submission.”

W3C’s example is a shop: “When an order is submitted, the order information—including items ordered, quantity of each ordered item, shipping address, and payment method—are displayed so that the user can inspect the order for correctness. The user can either confirm the order or make changes.” A single-screen checkout with an order summary the customer can read and edit satisfies this. An express one-tap flow that charges a card with no review step and no self-service cancellation does not.

The same list, sorted by who can fix it

Failure at checkoutCriterionLevelWho can fix it
Field with no programmatic label; placeholder used as label1.3.1, 3.3.2, 4.1.2AFixed automatically
Custom, plugin or rebuilt address field with no autocomplete token1.3.5AAFixed automatically
Icon-only remove, coupon or quantity control with no name4.1.2AFixed automatically
Low-contrast helper text, order summary or error notice1.4.3AAFixed after human approval
Error shown by red border or colour alone1.4.1ADetected and evidenced
Detected input error not described in text3.3.1ADetected and evidenced
Focus indicator removed by theme CSS2.4.7AADetected and evidenced
Focused field hidden behind a sticky header or cookie bar2.4.11AADetected and evidenced
Targets below 24 by 24 CSS pixels, insufficiently spaced2.5.8AADetected and evidenced
Login in the checkout path relying on recall3.3.8AADetected and evidenced
Focus not moved after a failed submission2.4.3AAudit only
No correction suggested for a known-format field3.3.3AAAudit only
Basket total or error summary never announced4.1.3AAAudit only
Field borders or selected states below 3:11.4.11AAAudit only
Billing and delivery address typed twice3.3.7AAudit only
No review, confirm or reverse before payment3.3.4AAAudit only

Six of those sixteen have no reliable automated detection in our engine, and they are not marginal: they are the error behaviour, the focus behaviour and the review step. That is the honest argument for walking the journey yourself.

Walking a real basket-to-order journey

Half an hour, one keyboard, one screen reader, one notebook. Do it on the checkout you actually serve, against a real product with real shipping options — not a test item priced at zero.

The keyboard pass. Unplug the mouse. From a category page: open a product, choose a variation, set the quantity, add to basket, change a quantity, remove a line, restore it, reach checkout, complete every field, choose a shipping method and a payment method, tick the terms box, place the order. Then break it deliberately — submit with an empty postcode and an invalid email — and see what happens next. At each step note one of three things: the control could not be reached, it could be reached but not operated, or focus went somewhere unexpected.

The specific things to catch, in the order we usually find them:

  1. Where does focus go after a failed submission? If the answer is “nowhere”, you have found the most expensive bug in the store.
  2. Can you see where you are on every control, including the shipping radios and the payment iframe?
  3. Does the sticky header or the cookie bar cover the focused field when you tab down the page?
  4. Does changing the country re-render the state field and drop your focus?
  5. Can you reach and operate the coupon toggle, the remove-item control and the quantity stepper?

The screen-reader pass. Same journey, listening. Does each field announce a name that tells you what to type, or “edit text, blank”? When the basket total changes over AJAX, is anything said at all? When submission fails, is the summary read out, and does it name the fields? Does the quantity field say which product it belongs to, or just “quantity” three times?

The zoom pass. Zoom in hard and narrow the window to phone width. Two-column checkouts, sticky order summaries and payment panels are where content overlaps, clips or scrolls in two directions.

Write the result as a list of barriers tagged with the step they occur on, not as a score. A list a developer can act on is worth more than any number, and the step matters because the complete-processes rule is evaluated step by step.

Why most scans never see your checkout

This is the part nobody says out loud. Point a typical scanner at a shop and it audits the home page, some category pages, perhaps a product page. It does not add anything to a basket. So it arrives at /checkout/ with an empty session, WooCommerce quite correctly tells it the basket is empty, and the checkout form — the whole reason you were scanning — never exists in the DOM to be audited. The report comes back clean on checkout because checkout was never tested.

Our engine works the other way round. It discovers the page set, then walks a ladder for a purchasable product id — a classic archive’s add-to-basket link, the add-to-cart parameter anywhere in the served HTML including the block markup’s own payload, a single product’s form, and failing all of those a read-only product listing from the store’s own API. It adds that product with the canonical add-to-basket request, confirms on the basket page that something is actually in the basket rather than assuming it, and only then forces the basket and checkout URLs into the scanned set rather than letting them fall off the end of a page budget. Checkout is audited as a hydrated page with a live form, by all three of the rule engines we run.

It also reports what it managed, in three states, because a silent pass is worse than a stated limit: checkout tested with a real basket; WooCommerce found but no product addable automatically, so the form was never reached with items in it; or no storefront found, so no checkout flow was exercised. A page that timed out or whose hydration could not be confirmed is flagged partial rather than counted clean. How it works sets out the mechanism per fix family.

Fixing it

Three ground rules first. Never edit plugin files; the next update overwrites you. Put the code in a small site plugin rather than the theme’s functions.php, so the work survives a redesign. And prefer a filter to a template override, because an overridden template quietly stops receiving upstream fixes — which is exactly how stores end up with WooCommerce’s own accessibility improvements sitting unused in a file they replaced three years ago.

Most guides reach for woocommerce_checkout_fields, the documented way to override the checkout field array. There is a better hook for this particular job: woocommerce_form_field_args runs on every field rendered through woocommerce_form_field(), including fields added by shipping and payment plugins that never appear in the checkout array. autocomplete is a first-class argument there — core copies a non-empty value straight onto the input — so filling in the blanks is enough, and core’s own tokens are left exactly as they are.

<?php
/**
 * Klarvo — checkout field hygiene for the classic checkout.
 *
 * Runs on every field rendered via woocommerce_form_field(), so it also catches
 * fields added by shipping and payment plugins. Two jobs:
 *   1. SC 1.3.5 — attach an autocomplete token where the field has none. Core
 *      already sets its own (section-prefixed) tokens; those are left alone.
 *   2. SC 3.3.2 / 1.3.1 — if a theme has emptied the label, put one back.
 */
add_filter(
	'woocommerce_form_field_args',
	function ( array $args, $key, $value ): array {
		// Tokens are the fixed values the HTML autocomplete attribute accepts.
		$tokens = array(
			'first_name' => 'given-name',
			'last_name'  => 'family-name',
			'company'    => 'organization',
			'address_1'  => 'address-line1',
			'address_2'  => 'address-line2',
			'city'       => 'address-level2',
			'state'      => 'address-level1',
			'postcode'   => 'postal-code',
			'country'    => 'country',
			'email'      => 'email',
			'phone'      => 'tel',
		);

		// billing_postcode and shipping_postcode share one token; strip the prefix.
		$base = preg_replace( '/^(billing|shipping)_/', '', (string) $key );

		if ( empty( $args['autocomplete'] ) && isset( $tokens[ $base ] ) ) {
			$args['autocomplete'] = $tokens[ $base ];
		}

		// A field with no label is a guess. Never overwrite a real one.
		if ( '' === trim( (string) $args['label'] ) && ! empty( $args['placeholder'] ) ) {
			$args['label'] = $args['placeholder'];
		}

		return $args;
	},
	20,
	3
);

Check the rendered attributes in the browser afterwards rather than trusting the filter. Themes and checkout plugins rewrite these arrays, a later hook can undo you, and the failure is silent — the code is present, the attribute is not.

On the block checkout, fields are registered rather than filtered, and the documented attributes array is where autocomplete goes:

<?php
add_action(
	'woocommerce_init',
	function () {
		woocommerce_register_additional_checkout_field(
			array(
				'id'         => 'klarvo/delivery-instructions',
				'label'      => __( 'Delivery instructions', 'klarvo' ),
				'location'   => 'address',
				'type'       => 'text',
				'required'   => false,
				'attributes' => array(
					'autocomplete'     => 'off',
					'aria-describedby' => 'klarvo-delivery-help',
				),
			)
		);
	}
);

The third fix is the belt and braces for the error path. Core covers the ordinary validation failure; what it does not cover is the generic-failure branch, and it stops covering anything at all the moment a theme, a builder or a checkout plugin replaces the notice markup. So check whether focus actually moved, and only step in when it did not.

// A region rendered once, early in the page, so assistive tech is already watching it.
// <div id="klv-status" role="status" aria-live="polite" class="screen-reader-text"></div>

jQuery( function ( $ ) {
  $( document.body ).on( 'checkout_error', function () {
    const group = document.querySelector( '.woocommerce-NoticeGroup-checkout' );
    if ( ! group ) return;

    // Core moves focus into the notice group on the normal validation path and
    // announces through a role="alert" wrapper. Do not announce twice.
    if ( group.contains( document.activeElement ) ) return;

    const summary = group.querySelector( '.woocommerce-error' ) || group;
    const count = group.querySelectorAll( '.woocommerce-error li' ).length;

    // Announce first: polite waits for a pause instead of interrupting.
    const status = document.getElementById( 'klv-status' );
    if ( status ) {
      if ( count === 1 ) {
        status.textContent =
          'There is 1 problem with your order. Details are above the form.';
      } else if ( count > 1 ) {
        status.textContent =
          `There are ${ count } problems with your order. Details are above the form.`;
      } else {
        status.textContent =
          'Your order could not be placed. Details are above the form.';
      }
    }

    // Then make the summary focusable and move to it, so the customer can read it.
    summary.setAttribute( 'tabindex', '-1' );
    summary.focus( { preventScroll: false } );
  } );
} );

checkout_error is the event WooCommerce triggers on document.body after a failed submission, and .woocommerce-NoticeGroup-checkout is the wrapper it prepends. Three details do the work: the guard, so this adds nothing on the path core already handles; the live region existing before the change, because inserting a region and its text in the same tick often announces nothing; and moving focus, which is what turns a correct announcement into a usable one, because the customer ends up reading the errors rather than being told from the bottom of the form that some exist. The count is zero-safe on purpose — the generic-failure branch has no list items to count.

What automation can and cannot do at this step

This is our own capability data rather than a market claim. We track fifty-five Level A and AA success criteria — thirty-one at Level A, twenty-four at Level AA, drawn from WCAG 2.0, 2.1 and 2.2 — across ten fix families. Thirty-nine have some automated detection. Sixteen have none, and a person has to test them.

Of the sixteen checkout failures tabled above: three are detected and remediated server-side in the page HTML; one, contrast, is detected and proposed but only applied once a person approves it, because it is a brand decision; six are found, located and reported with the offending markup for a developer to change; and six have no reliable automated detection at all. Four of those six reported ones — focus visible, focus not obscured, target size and accessible authentication — run through probes we wrote ourselves. For focus not obscured and accessible authentication those probes are the only detection there is, because none of the third-party rule engines we run has a rule for either.

That is the division of labour, and it is worth stating without hedging. Software is very good at the mechanical and unambiguous: a field with no label, a missing autocomplete token, a nameless remove button, an unreadable notice colour. It applies those fixes server-side in the page HTML, never as a client-side overlay. Software is not good at judgement and does not pretend to be — whether an error message suggests a correction, whether focus lands somewhere sensible after a shipping change, whether a customer can review and reverse a mistaken order. An automated finding is evidence, not a certificate. The claim that a store conforms belongs to a qualified human audit, at a stated scope and on a stated date, which is what the audit tier is for.

In January 2025 the US Federal Trade Commission announced a settlement requiring an accessibility-software vendor to pay one million dollars over claims that its automated product could make any website conform to WCAG. The order bars the company from representing that its automated products “can make any website WCAG-compliant or can ensure continued compliance with WCAG over time, unless it has the evidence to support such claims”. Evidence is the operative word, and it is why our reports show you the markup.

Keeping the checkout fixed after the next deploy

Checkout is the most volatile page in a WooCommerce store, so it drifts fastest. A payment gateway update replaces its own field markup. A shipping plugin adds a field your filter has never seen. A seasonal promo bar becomes sticky and covers the focused input. None of that makes a store “become inaccessible” — it changes, and conformance is evaluated against what is there now.

So run the journey, not just the scan, after every plugin or theme update that touches checkout or payment, and after any redesign. Keep the basket and checkout inside whatever set you re-scan, which means the scan has to be able to reach them with items in the basket. And treat re-testing as a recurring service rather than a one-off purchase: what a human audit can state, it states for the scope it examined on the date it examined it, and your checkout on Friday is not the checkout that was audited on Monday.

For the starting position, run the free scan at app.klarvoaccess.com against your shop and let it reach the checkout with a real basket. It will not tell you everything — nothing automated will, and the six audit-only rows above are the proof — but it will tell you which of these sixteen you are dealing with, and it will tell you plainly if it could not get to your checkout at all.

On this subject

The fixes this guide refers to:

The other guides

Sources

Written by the Klarvo Access team. Published 2026-08-04.

FAQ

Questions this raises

Is the block checkout more accessible than the classic checkout?

Neither is conformant on its own, and the difference between them matters less than what your theme and your checkout plugins do to either one. Recent WooCommerce core is better than most guides assume — required fields carry aria-required, the visual asterisk is hidden from assistive technology, the default address fields ship autocomplete tokens, and inline field errors set aria-invalid and aria-describedby. What breaks that is a stale template override, a checkout plugin that re-renders the form, or a page builder that rebuilds the markup. Test the checkout you actually serve, on the device your customers actually use.

Can a plugin make my checkout accessible on its own?

It can remove a real, large class of failures and evidence the rest, and that is worth doing first because those failures are the cheap ones. It cannot finish the job. Of the fifty-five Level A and AA criteria we track, sixteen have no reliable automated detection at all, and six of those sixteen land directly on checkout — focus order, error suggestion, error prevention for financial transactions, redundant entry, status messages and non-text contrast. Nor can any software certify the result; only a qualified human audit can do that, at a stated scope and on a stated date.

Does WCAG require me to move focus to the error summary?

Not as a specific mechanism. SC 3.3.1 requires that a detected input error is identified and described in text, and SC 4.1.3 requires that a status message can be presented by assistive technology without receiving focus. Moving focus to the summary is not the only way to satisfy those, but in a WooCommerce checkout it is the most reliable one, because it also solves the practical problem that the customer is left at the bottom of a long form with no idea that anything appeared above it.

My checkout is a third-party one-page checkout plugin. Does any of this still apply?

All of it, and the conformance rules make that explicit. W3C states that conformance is for full web pages only and cannot be achieved if part of a web page is excluded, so a checkout someone else wrote is inside your scope, not outside it. In practice a replacement checkout is where we find the most failures, because it re-implements the field rendering, the validation and the notices that WooCommerce core has slowly been fixing.

Does the European Accessibility Act say anything specific about payment?

Yes, and it is unusually direct. Annex I of Directive (EU) 2019/882 sets additional requirements for e-commerce services, including ensuring the accessibility of the functionality for identification, security and payment when delivered as part of a service instead of a product by making it perceivable, operable, understandable and robust, and providing identification methods, electronic signatures, security and payment services which are perceivable, operable, understandable and robust. A Directive is implemented through national law, so how it reaches your shop is a question for your own legal advice.

Scan the store this applies to.

Three engines, nine probes, your real pages — home, product, populated basket, hydrated checkout. Free, no signup, and the findings are yours whether or not you buy anything.