Checkout is where a bug costs money directly. This is the checklist we would run against a card payment form before shipping it — grouped by what breaks, with the test data each group needs.
Two things it assumes: that you are using generated test numbers for the form-level checks and your gateway’s sandbox cards for the processor-level ones, and that you are testing on a real mobile device somewhere in the process, not only in a desktop emulator.
A copy-paste version of the whole thing is at the bottom.
1. Card number field
- 13, 14, 15, 16 and 19-digit numbers are all accepted
- Input is
type="text", nevertype="number"— the latter strips leading zeros and renders spinner arrows -
inputmode="numeric"for a numeric keypad on mobile -
autocomplete="cc-number"so browsers and password managers can fill it - Spaces and dashes are stripped or accepted, not rejected
- Paste works, including a number pasted with its formatting
- The mask follows the length: 4-4-4-4, 4-6-5, 4-6-4, 4-4-4-4-3
-
maxlengthaccommodates 19 digits plus separators - Letters and symbols are blocked or silently stripped
- The Luhn check runs on blur, not on every keystroke
- A failed Luhn check warns but does not block submission
- The same validation runs server-side
The lengths are the ones that catch people — which network uses which is the reference, and the Luhn guide covers why the checksum is advisory rather than authoritative.
Test data: the generator for individual cases, the bulk generator for a fixture file.
2. Card brand detection
- The logo appears as soon as the prefix is unambiguous
- No logo is shown while the prefix is still ambiguous
- A 2-series Mastercard is recognised
- American Express is recognised and the security code field widens to four digits
- Maestro, Discover, JCB, UnionPay and Diners Club are recognised
- An unrecognised prefix is accepted rather than blocked
- Clearing the number clears the logo
- Changing brand changes the mask
Detection that commits too early shows the wrong logo and then corrects itself, which reads as a bug on the one field where customers are already careful. The brand detection guide has boundary-tested patterns, and the Mastercard page covers the 2-series specifically.
3. Expiry date
- Month is validated as 01–12
- A past date is rejected
- The current month is accepted — cards expire at the end of their month, not the start
- Both two-digit and four-digit years are handled
- The
MM/YYseparator is inserted automatically - Dates more than ten years out are accepted
-
autocomplete="cc-exp-month"andcc-exp-yearare set
The third item is the one that reaches production. A rule requiring the expiry to be strictly later than today rejects every card in its final month — a working card, refused, with the customer told to try another.
4. Security code
- Four digits on American Express, three on everything else
- The field length updates when the detected brand changes
- Only digits are accepted
-
autocomplete="cc-csc"is set - Help text and any diagram show the correct location — Amex prints it on the front
- The code is never written to form state persistence,
localStorage, logs, analytics or error reports - A missing code is handled for cards that do not carry one
Why a security code cannot be derived from the number explains the storage rule, and the CVV generator emits both lengths. The Amex generator is the fastest way to exercise the four-digit branch.
5. Cardholder name
- Accented characters are accepted: ö, ü, ç, é, ñ, ø, å
- Cyrillic, Greek and Arabic scripts are handled or explicitly rejected with a clear message
- Hyphens and apostrophes are accepted — O’Brien, Jean-Luc
- Very long names do not overflow or truncate silently
- Single-word names are accepted; not every culture uses a surname
-
autocomplete="cc-name"is set - Encoding survives intact all the way to the database — UTF-8 end to end
This section is skipped more often than any other and it costs international customers directly. The identity generator produces names with characters worth testing against.
6. Billing address and AVS
- Postal codes are validated per country, with no assumption of five digits
- Postal codes are stored as strings, never integers — leading zeros are real
- State or province is required or optional according to the country
- An AVS response of
G— issuer does not participate — is not treated as a failure - A billing address different from the shipping address works
- Address autocomplete does not prevent manual entry
The AVS item matters for international traffic: a foreign issuer that does not participate in address verification returns a code meaning “cannot check”, and code that treats anything other than a full match as fraud declines those customers wholesale.
7. Submission and processing
- Double-clicking submit does not create two payments — disable the button and send an idempotency key
- A loading state is visible while the request is in flight
- Network failure is handled with a message the customer can act on
- Timeouts are handled, and the outcome is checked rather than assumed
- Slow connections show progress rather than appearing frozen
- The browser back button after payment does not resubmit
- Refreshing the page does not charge twice
8. Error handling
- The decline message is human — “Your bank declined this payment” — not a raw code
- The decline reason is never shown to the customer; “lost card” and “stolen card” must not reach the screen
- Cart and form contents survive a failure, minus the card number
- Retry is possible but rate-limited
- Repeated failures from one source are blocked
- Errors are announced to screen readers via
aria-live
The second item is both a safety and an accuracy question: those codes are often wrong, and when they are right, displaying them tells the wrong person something useful. Why cards get declined covers the categories, and the card fraud guide covers why repeated failures need a limit.
9. 3-D Secure
- The frictionless flow completes
- The challenge flow completes
- A cancelled challenge returns the customer to a usable state
- A soft decline is retried with authentication rather than shown as a failure
- The challenge iframe renders correctly on a mobile viewport
- The webhook arriving before the browser returns is handled
The 3-D Secure guide has the full sixteen-item matrix, including the exemption paths and the race condition.
10. Security and compliance
- The page is served over HTTPS, with no mixed content
- Card fields live in hosted fields or an iframe, keeping the PAN out of your DOM
- A Content Security Policy is enforced on the payment page
- Third-party scripts on checkout are minimised and carry Subresource Integrity
- No PAN appears in application logs — verified by grepping the logs, not by reading the code
- No security code is written anywhere at all
- The error tracker’s payloads contain no card data
- Analytics events contain no card data
- Browser autofill does not populate unexpected fields
The PCI DSS guide lists the ten places card data lands without anyone deciding to store it, and tokenisation is how you stop holding it at all.
11. Accessibility
- Every field is reachable and operable by keyboard
- Labels are real
<label>elements, not placeholder text - Error messages are associated with their field via
aria-describedby - Focus order follows the visual order
- Contrast ratio is at least 4.5:1, including on error states
- The form has been tested end to end with a screen reader
- Decorative card artwork is
aria-hidden
12. Mobile
- Tested on a real device, not only an emulator
- The numeric keypad opens for number fields
- Autofill works, including card scanning on iOS
- Fields stay visible when the keyboard is open
- Touch targets are at least 44×44 px
- Landscape orientation does not break the layout
An emulator gets you the layout. It does not get you the keyboard behaviour, the autofill prompt, the scan-card affordance, or the way a real thumb hits a 32-pixel target.
13. Automation
- There is an end-to-end test for the critical path
- Test data is version-controlled and deterministic
- Negative cases are included, not only the happy path
- Tests do not depend on a live gateway — mock it or use the sandbox
- The suite runs in CI on every change
The bulk generator produces deterministic fixture sets, and test data management covers keeping them healthy. When a test genuinely needs a processor response, the sandbox card reference has the numbers.
The whole list, to copy
## Card number field
- [ ] 13, 14, 15, 16 and 19-digit numbers accepted
- [ ] type="text", not type="number"
- [ ] inputmode="numeric"
- [ ] autocomplete="cc-number"
- [ ] Spaces and dashes stripped or accepted
- [ ] Paste works with formatted numbers
- [ ] Mask follows length (4-4-4-4, 4-6-5, 4-6-4, 4-4-4-4-3)
- [ ] maxlength covers 19 digits plus separators
- [ ] Letters and symbols blocked or stripped
- [ ] Luhn check runs on blur
- [ ] Failed Luhn warns, does not block
- [ ] Validation repeated server-side
## Brand detection
- [ ] Logo appears when the prefix is unambiguous
- [ ] No logo while ambiguous
- [ ] 2-series Mastercard recognised
- [ ] Amex recognised, security code field widens to 4
- [ ] Maestro, Discover, JCB, UnionPay, Diners recognised
- [ ] Unrecognised prefix accepted, not blocked
- [ ] Clearing the number clears the logo
- [ ] Brand change updates the mask
## Expiry date
- [ ] Month validated 01-12
- [ ] Past dates rejected
- [ ] Current month accepted
- [ ] Two and four-digit years handled
- [ ] MM/YY separator inserted automatically
- [ ] Dates 10+ years out accepted
- [ ] autocomplete="cc-exp-month" / "cc-exp-year"
## Security code
- [ ] 4 digits on Amex, 3 elsewhere
- [ ] Field length updates with detected brand
- [ ] Digits only
- [ ] autocomplete="cc-csc"
- [ ] Help text shows correct location (Amex: front)
- [ ] Never persisted anywhere
- [ ] Missing code handled where a card has none
## Cardholder name
- [ ] Accented characters accepted
- [ ] Non-Latin scripts handled or clearly rejected
- [ ] Hyphens and apostrophes accepted
- [ ] Very long names handled
- [ ] Single-word names accepted
- [ ] autocomplete="cc-name"
- [ ] UTF-8 intact to the database
## Billing address and AVS
- [ ] Postal code validated per country
- [ ] Postal code stored as a string
- [ ] State/province required per country rules
- [ ] AVS "G" not treated as failure
- [ ] Billing separate from shipping works
- [ ] Autocomplete does not block manual entry
## Submission
- [ ] Double-click does not double-charge
- [ ] Loading state visible
- [ ] Network failure handled
- [ ] Timeout handled
- [ ] Slow connection shows progress
- [ ] Back button safe after payment
- [ ] Refresh does not charge twice
## Error handling
- [ ] Human decline message, no raw codes
- [ ] Decline reason never shown to the customer
- [ ] Cart and form survive failure
- [ ] Retry possible but rate-limited
- [ ] Repeated failures blocked
- [ ] Errors announced via aria-live
## 3-D Secure
- [ ] Frictionless flow completes
- [ ] Challenge flow completes
- [ ] Cancelled challenge recoverable
- [ ] Soft decline retried with authentication
- [ ] Challenge iframe correct on mobile
- [ ] Webhook-before-redirect handled
## Security and compliance
- [ ] HTTPS, no mixed content
- [ ] Card fields in hosted fields or an iframe
- [ ] CSP enforced on the payment page
- [ ] Third-party scripts minimised, SRI applied
- [ ] No PAN in logs (verified by grep)
- [ ] No security code stored anywhere
- [ ] No card data in error tracker payloads
- [ ] No card data in analytics
- [ ] Autofill does not populate unexpected fields
## Accessibility
- [ ] Keyboard operable throughout
- [ ] Real <label> elements
- [ ] Errors linked via aria-describedby
- [ ] Logical focus order
- [ ] Contrast at least 4.5:1
- [ ] Screen reader tested end to end
- [ ] Decorative card art aria-hidden
## Mobile
- [ ] Tested on a real device
- [ ] Numeric keypad opens
- [ ] Autofill and card scanning work
- [ ] Fields visible with keyboard open
- [ ] Touch targets at least 44x44 px
- [ ] Landscape layout intact
## Automation
- [ ] E2E test for the critical path
- [ ] Test data version-controlled and deterministic
- [ ] Negative cases included
- [ ] No dependency on a live gateway
- [ ] Runs in CI
Ninety-three items. Most teams pass the first two sections and fail somewhere between five and twelve, which is roughly the order in which the failures reach customers.