Teardown · Online checkout

Fixing a bakery's online checkout

Crumbwell takes pickup orders for pastries and cakes. It looks ready to launch. It isn't: change one number and a €42 cake costs one cent, change the order link and you can read another customer's phone number, and on a phone the Place order button sits under the cart bar.

Crumbwell is a sample app I built for this teardown, the way AI app builders often ship them. There's no real bakery and no real customers. The problems, the fixes and every number below are real, measured on the live site before and after.

The results

Price of a €42 cake, if you edit the order
€0.01€42
Accessibility (Lighthouse)
78100
WCAG issues found by axe-core (menu and checkout)
300
Performance on a phone (Lighthouse)
7599
Main content visible, phone on 4G
5.0 s1.7 s
Page weight
5.05 MB0.55 MB

Measured on the live sites with Lighthouse (mobile, simulated 4G), axe-core on WCAG 2.2 AA rules, and scripted orders against the real API. Same settings, before and after.

Watch the whole fix in 3 minutes

From the bugs, to the redesign in Penpot, to test-first fixes and the live numbers. Captions are on by default.

Checkout on a phone

Before: the Crumbwell checkout on a phone, scrolled to the bottom. Fields have only faint placeholder text, pickup times are in AM/PM boxes, and a dark View cart bar covers the bottom of the screen where Place order should be.
Before. Scrolled to the end, and Place order is still hidden under the cart bar.
After: the Crumbwell checkout on a phone. Day and time choices are clear buttons marked Lisbon time, the order summary is readable, and the total sits next to a large Place order button fixed at the bottom.
After. The total and Place order stay on screen the whole way down.

The diagnosis

This is the kind of written review an audit or a diagnostic gives you: what's wrong, why it matters, and what fixes it, worst first.

Critical

The browser set the price

The checkout sent its own total to the server, and the server believed it. I edited one number in the request and ordered the €42 celebration cake for €0.01. The order went through.

// server: creating an order
- const total = req.body.total;          // whatever the browser says
+ const total = priceFromMenu(req.body.items); // the server's own prices

The server prices every order from its own product list and ignores any total sent by the browser. The same €0.01 order is now charged €42.

Critical

Order links showed other customers' details

Order pages lived at addresses like /order/1042. Change the number to 1041 and you saw someone else's name, phone number and order. Anyone could read every customer this way.

Each order gets a random, unguessable 22-character link. The old numbered links now return "not found".

High

Pickup times in the wrong time zone

Pickup slots were built in UTC, not Lisbon time, then shown in the customer's own time zone. From late March to late October the list ran an hour late: 9:00 to 18:00 instead of 8:00 to 17:00, so the bakery offered a pickup after it had closed. A customer abroad saw different times again.

Slots are built in Europe/Lisbon time, labelled "All times Lisbon time", with tests across both clock changes.

High

A checkout keyboard and screen-reader users couldn't finish

The pickup-time picker was a grid of clickable boxes a keyboard can't reach. Fields had only faint placeholders instead of labels, errors were shown only in red, the order button had 2.1:1 contrast and the grey text 2.3:1. The menu photos had no descriptions and the page didn't declare its language. That's lost orders, and a legal risk under the European Accessibility Act.

The checkout was redesigned in Penpot first, then built to match: real radio buttons for day and time, a label on every field, errors in words, and colours that pass WCAG 2.2 AA. axe-core now finds 0 issues on the menu and the checkout.

High

Place order hidden on phones

The "View cart" bar stayed fixed at the bottom of the screen on the checkout page too, right where the Place order button was. On a phone you couldn't see the button at all.

No cart bar on checkout. The total and Place order stay visible at the bottom, checked by an automated phone test at every scroll position.

Medium

5 MB of photos

Full-size camera photos were sent to every visitor, phone or not, and the fonts and menu loaded separately after the page.

Resized, responsive WebP photos (5.0 MB to 0.35 MB), self-hosted fonts, and the menu shipped with the page so nothing jumps while it loads.

How the fix went

  1. 01

    Measure

    Lighthouse, axe-core and scripted orders against the live API, so "before" is numbers, not opinions.

  2. 02

    Design

    The checkout redesigned in Penpot: labels, a keyboard-friendly time picker, errors in words, an order button that stays on screen.

  3. 03

    Tests, then fixes

    A failing test for each problem, then the change that makes it pass. Six fixes in about forty minutes, each in its own commit.

  4. 04

    Ship and re-measure

    Deployed, then every check re-run on the live site, including the one-cent order and the old order link.

Try both versions yourself: the broken before and the fixed after (sample data that resets; please don't enter real details). Each fix is a separate commit, so you can see what changed and why: read the code on GitHub. Another example: the Classly teardown of an AI-built booking app.

Work with me

Your shop

Is your checkout this broken?

An accessibility audit tests your key pages and checkout with keyboard and screen reader, for the European Accessibility Act, the ADA and Section 508. You get a plain-language fix list and a re-check.

Your agency

Need this for a client, under your brand?

Start with one client site: a white-label WCAG 2.2 AA audit, report in 48 hours, €750. No call needed.