# Checkouts that take payment with no review or undo

Source: https://easeweb.dev/learn/error-prevention
Topics: Forms > Payment and legal steps can be checked or undone (WCAG 3.3.4)
The fix: Either add a step that reads the booking back with a Change link for each choice before a "Confirm and pay" button, or state a time within which the booking can be cancelled for a full refund and put a Cancel button on the confirmation. A required "I've checked" box beside a summary also passes, but leaves people who tick it by habit paying for the slip.
Test with: keyboard, screen reader, zoom, touch
References: https://www.w3.org/WAI/WCAG22/Understanding/error-prevention-legal-financial-data.html https://www.w3.org/WAI/WCAG22/Techniques/general/G98 https://www.w3.org/WAI/WCAG22/Techniques/general/G164 https://www.w3.org/WAI/WCAG22/Techniques/general/G155 https://design-system.service.gov.uk/patterns/check-answers/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A theatre's booking page asks for two things: which performance, Friday 13 November or Saturday 14 November, and how many tickets, at $24 each. The visitor is signed in, so their card is saved. Under the fields, a line says tickets can't be refunded or exchanged, and a button says Pay now.

They want one ticket for Friday. On the way, their hand slips and the number goes up to 2. They press Pay now. The card is charged $48 at once, and the next page says "Booking confirmed", with no way to change it and no way to cancel. The mistake was there to see, but nothing showed it to them before it counted.

## Why it fails

Success criterion 3.3.4, Error Prevention (Legal, Financial, Data), is level AA. When a page causes a legal commitment or a financial transaction, changes or deletes data people control, or submits their test answers, at least one of three things must be true. The submission can be reversed; the entries are checked for input errors and people can correct them; or there is a way to review, confirm and correct the information before it is final. This form does none of them.

For a purchase, W3C's sufficient techniques are a step to review and correct the answers before submitting (G98), a stated time within which the order can be changed or cancelled after it is placed (G164), and a checkbox to confirm, in addition to the submit button (G155). Checking entries is the second way in the criterion, but it covers input errors: a value missing, or outside what the form accepts. Two tickets and the wrong night are valid choices that no check can catch, and W3C lists client-side validation only as advisory for this criterion. Validation still matters for 3.3.1 and 3.3.3; it isn't the fix for a valid choice that was never meant.

The sentence about refunds doesn't help either. It tells people the mistake will be permanent; it doesn't give them a chance to find it.

## Who is affected

The cost lands on the people least able to absorb it. A wrong booking for someone on a tight budget can mean going without, and asking for an exception means a phone call or an email, which are their own barriers.

## What automation and AI miss

Each field has a label, the button has a name, and the page passes every automated check. Whether an order can be reviewed or cancelled is spread over several pages and the server behind them, and a scanner never presses Pay on a live site. EaseWeb's audit flags forms that take a payment or place an order for a person to check, but it doesn't submit them, so it can't tell which of the three ways, if any, a site uses. A code assistant asked to "prevent errors" on this form adds `min` and `max` to the number field, which stops 0 and 99 tickets but not 2.

## Before: Pay now charges the card at once

```html book.html
<form method="post" action="/book">
  <label for="night">Performance</label>
  <select id="night" name="night">
    <option>Friday 13 November, 7:30 pm</option>
    <option>Saturday 14 November, 7:30 pm</option>
  </select>
  <label for="tickets">Tickets ($24 each)</label>
  <input id="tickets" name="tickets" type="number" value="1">
  <p>Paid with your saved card ending 4242. Tickets can't be refunded or exchanged.</p>
  <button type="submit">Pay now</button>
</form>
```

The server charges the saved card and issues the tickets as soon as the form arrives. Nothing reads the choices back, nothing asks for confirmation, and nothing can be cancelled, so one press is final.

## After: a step to check the booking before paying

```html book.html
<!-- Step 1: the same form, but Continue only saves the choices -->
<form method="post" action="/book/review">
  <label for="night">Performance</label>
  <select id="night" name="night">
    <option>Friday 13 November, 7:30 pm</option>
    <option>Saturday 14 November, 7:30 pm</option>
  </select>
  <label for="tickets">Tickets ($24 each)</label>
  <input id="tickets" name="tickets" type="number" value="1">
  <p>Paid with your saved card ending 4242. Tickets can't be refunded or exchanged.</p>
  <button type="submit">Continue</button>
</form>
```

```html review.html
<!-- Step 2: Check your booking -->
<h1>Check your booking</h1>
<dl>
  <dt>Performance</dt>
  <dd>
    Friday 13 November, 7:30 pm
    <a href="/book#night">Change<span class="visually-hidden"> performance</span></a>
  </dd>
  <dt>Tickets</dt>
  <dd>
    2
    <a href="/book#tickets">Change<span class="visually-hidden"> number of tickets</span></a>
  </dd>
  <dt>Total</dt>
  <dd>$48, paid with your saved card ending 4242</dd>
</dl>
<p>Tickets can't be refunded or exchanged after you pay.</p>
<form method="post" action="/book/confirm">
  <button type="submit">Confirm and pay $48</button>
</form>
```

Continue saves the choices without charging anything, and the next page reads them back in words, with the total. Each Change link goes back to its field with every answer kept, so fixing the slip takes one change, not a new booking. Only "Confirm and pay $48" charges the card, and its name says how much. The heading and the page's title say which step this is, so screen reader users know they have arrived at the check, not at a confirmation.

## After: a stated time to cancel, with a Cancel button

```html book.html
<!-- The booking form: the cancel time is stated before paying -->
<form method="post" action="/book">
  <label for="night">Performance</label>
  <select id="night" name="night">
    <option>Friday 13 November, 7:30 pm</option>
    <option>Saturday 14 November, 7:30 pm</option>
  </select>
  <label for="tickets">Tickets ($24 each)</label>
  <input id="tickets" name="tickets" type="number" value="1">
  <p>Paid with your saved card ending 4242. You can cancel for a full refund until 6 pm the day before the performance.</p>
  <button type="submit">Pay now</button>
</form>
```

```html confirmation.html
<!-- On the confirmation page, and in the confirmation email -->
<h1>Booking confirmed</h1>
<p>2 tickets for Friday 13 November, 7:30 pm. $48 paid with your card ending 4242.</p>
<p>You can cancel this booking for a full refund until 6 pm on Thursday 12 November.</p>
<form method="post" action="/bookings/B7Q2/cancel">
  <button type="submit">Cancel booking</button>
</form>
```

The order is placed at once, but it can be taken back. The time is stated before people pay and again afterwards, as an actual date and time, and cancelling takes one button on the page they are already on. This holds only while the window is long enough to notice a mistake in, and while cancelling is no harder than booking: no phone call, no form to fill in, no hunting for a link in an account page. The money still leaves the account and comes back days later, and a slip noticed after the window closes is still paid for.

## After: a box to tick, beside a summary of the order

```html book.html
<form method="post" action="/book">
  <label for="night">Performance</label>
  <select id="night" name="night">
    <option>Friday 13 November, 7:30 pm</option>
    <option>Saturday 14 November, 7:30 pm</option>
  </select>
  <label for="tickets">Tickets ($24 each)</label>
  <input id="tickets" name="tickets" type="number" value="1">
  <p>Paid with your saved card ending 4242. Tickets can't be refunded or exchanged.</p>
  <p id="summary">1 ticket for Friday 13 November, 7:30 pm: $24</p>
  <label>
    <input type="checkbox" id="order-checked" name="order-checked" required
      aria-describedby="summary">
    I've checked the performance and the number of tickets
  </label>
  <button type="submit">Pay $24</button>
</form>
```

```js
const form = document.querySelector('form');
const box = document.getElementById('order-checked');
form.addEventListener('input', (event) => {
  if (event.target === box) return;
  // The order changed: say what it is now, and ask for the check again.
  box.checked = false;
  const tickets = Number(form.tickets.value);
  const total = `$${tickets * 24}`;
  document.getElementById('summary').textContent =
    `${tickets} ${tickets === 1 ? 'ticket' : 'tickets'} for ${form.night.value}: ${total}`;
  form.querySelector('button[type=submit]').textContent = `Pay ${total}`;
});
```

This is the smallest change: the form stays on one page. The summary sits next to the box and is read with it, the button names the total, and the box is required, so the browser stops the payment and says why if it isn't ticked. Changing the order after ticking unticks the box, so what was confirmed is what is paid. It is not enough on its own: a box asks people to check without showing them anything new, many tick it by habit, and people who zoom in or use a screen reader can't take in the summary and the fields together.

## Which option to use

All three pass 3.3.4. Use the review step: it stops the slip before any money moves, for every kind of order, and needs no refund process behind it. Add a cancel window as well when the business can take bookings back; it also catches mistakes people only notice later, such as a clash with another plan. Use the checkbox only on a one-page checkout that can't gain a step yet, and replace it with a review when you can.

## Implementation decisions

Keep the choices on the server between the steps, in the booking session, not only in the page. Then the Back button, a Change link and a reload all come back with every answer in place, as [checkout forms that ask for the same address twice](/learn/redundant-entry) explains.

On the review step, write each choice out in full and as people would say it: "Friday 13 November, 7:30 pm", not "13/11 19:30" or a seat code. Give the total, how it will be paid, and any condition that matters, such as no refunds, before the button.

A payment provider's hosted page often charges as soon as its own button is pressed. Put your review step before handing over, so the provider's page is the last step and says what it will take.

Some actions need the same care without money: deleting an account or a project, sending a signed form, submitting an exam. For a delete, a confirmation that says what will be lost, or an Undo right after, does the job; see [confirmation dialogs that don't read out their question](/learn/confirmation-dialogs) and [toasts that vanish before you can read or undo them](/learn/toast-timing).

Don't disable the final button to prevent a second press. Make the server accept each order once, with a token in the form, so a double press books once and the button still works if the first press failed.

## Verify the fix

1. Make a test booking with a deliberate slip, such as one extra ticket or the wrong performance, and go as far as the site lets you before money is taken. Confirm the slip is shown to you, in words, before it counts, or that you can cancel it afterwards.
2. From the review step, use each Change link. Confirm it goes to the right field with every answer kept, and that the review shows the corrected order.
3. Where there is a cancel window, confirm its end is stated as a date and time before paying and after, and cancel a test booking from the confirmation page with a keyboard alone.
4. With a screen reader, confirm every choice and the total are read before the button that commits, and that the button's name says what it does.
5. At 400% zoom or on a phone, confirm the summary and the final button can be read together, or one straight after the other.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the checkout's steps or its payment provider change.

## Limits

This is a starting pattern, not a guarantee for every process. The example is a ticket booking; shops, subscriptions, loans, applications and tests need the same check at the step that commits. 3.3.4 doesn't ask for a confirmation every time someone saves or edits their own data, only where a mistake would be costly or hard to undo. Test the complete process, including the emails it sends, with the people and tools your users rely on.

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
