VapeHub Checkout — Developer Spec
What the checkout prototype demonstrates and what the production build must supply. Where the prototype fakes something, it says so and points at the real source. Companion to the design pack's checkout brief.
The checkout is a standalone page (/checkout/) with its own stripped header (logo + "Secure checkout" only — no nav/search/menu, deliberately, to remove exits at the pay step). Real VapeHub design system, not the mock's chrome.
1. Age verification — the critical flow
VapeHub legally must confirm buyers are 18+. The wrong build (an up-front ID/DOB wall) destroys conversion. The right one is silent electronic verification, pre-signposted.
- Signpost, don't gate. A calm block in Delivery explains age is confirmed instantly and automatically at payment — no DOB field or upload up front.
- On order submission (after payment auth): run an electronic age check on name + address via a data provider (Experian / TransUnion / Yoti / AgeChecked — 18+ electoral-roll / credit-header match). Invisible to the ~90%+ who pass.
- Pass → order proceeds to dispatch, customer sees nothing extra.
- Fail / inconclusive → hold the order (auth-not-captured, or captured with a clear refund path) and trigger a one-off ID step (e.g. Yoti / photo-ID upload) via a secure emailed link. Dispatch only after pass.
- Decline → cancel + refund with a clear message.
The 18+ tickbox (built): an explicit "I confirm I am 18 or over" above the Pay button.
- Never pre-ticked — must be an affirmative act.
- Blocks submission when unticked: Pay stays enabled (a disabled button with no reason is worse UX), but clicking without the tick shows an announced inline error (
role="alert"), flashes the box, scrolls it into view and moves focus to the checkbox. Ticking clears it. - Store the attestation against the order — value + timestamp — for the compliance trail. It is a legal attestation, necessary but not sufficient; the electronic check does the real verification.
Confirm before building the failure-path UI: which AV provider VapeHub uses, and its exact pass/fail/inconclusive handling + ID-step UX + retention rules (UK GDPR — never store ID docs longer than needed).
2. Delivery address — postcode lookup
The mock's lookup is hardcoded (sample Bolton addresses). Production wires a real address API (Loqate / getAddress.io / Royal Mail PAF): postcode → matching addresses → select → populate. Keep the interaction (find → pick → populate) and always keep "enter address manually" available. Correct autocomplete tokens on every field (name, address-line1, address-line2, address-level2, postal-code) so browser autofill works.
3. Delivery method + dispatch countdown
- Radio options config-driven from the shipping engine: Free Next-Day (pre-selected when subtotal ≥ £30, Royal Mail Tracked) · Standard £2.99 (2–3 days). Selecting updates the summary + total.
- Dispatch countdown (same as basket): cutoff 16:00 UK, Mon–Fri; before cutoff "Order within {Xh Ym Zs} for same-day dispatch" (tabular-nums); after/weekend/holiday "Order now — ships {next working day}". Real/server time; never resets per visit. Needs a working-days + bank-holiday calendar.
4. Payment
- Cards only now via Worldpay. Use Worldpay's hosted fields / iframe for card number / expiry / CSC / name — correct
autocomplete (cc-number, cc-exp, cc-csc, cc-name), inputmode=numeric. Do not post raw PAN through your own form (PCI scope). - On failed payment, preserve all entered data and show a clear retry message.
- Wallet express-pay slot is reserved above the CTA. When live: Apple Pay + Google Pay first (Worldpay supports both; normal card rate; can pre-fill address). PayPal / Klarna pending eligibility — frequently prohibited for vape/nicotine; confirm merchant approval first.
5. Contact + consent
- Guest checkout is the default and visible (green reassurance bar + quiet "Sign in"). Never force account creation — the #1 checkout killer. Offer optional post-purchase account creation on the confirmation page.
- Email only (order confirmation); validate on blur.
- Marketing consent checkbox opt-in / unticked (UK GDPR + PECR).
6. Order summary rail
- Mirrors the basket state exactly (same cart, same maths — multibuy, delivery, loyalty). Read-only here; link back to the basket to edit.
- Per line: borderless tinted thumbnail · "{qty} × {name}" · flavour · strength · per-line multibuy saving with the full price struck through inline. Totals: Subtotal (after multibuy) · Delivery · Total. Loyalty line uses the real earn rate (shared with basket).
- Trust rows are monoline SVG icons, not emoji (package, return, shield). Full-height sticky rail; recompute on any change;
aria-live on the total.
7. Validation, a11y, performance
- Inline, on-blur validation per field (not one dump on submit); specific messages.
aria-live="polite" on total + validation messages (not the per-second countdown). <label> + autocomplete on every field; logical tab order; visible focus rings; keyboard-operable delivery radios; prefers-reduced-motion respected.- SSR the checkout — it must not be a client-only render that dies without JS (the review's #1 finding). No CLS; skeleton while cart/totals load. Mobile: single column, top summary accordion, inputs ≥16px (prevent iOS zoom), zero horizontal overflow at 390px, viewport meta.
8. Data model the back-end needs
- Cart (shared with basket): per line
productId, name, variant, resolved variant SKU, unitPrice, qty, image, multibuy {n, price} or promo-group ref, stock. - Config:
freeDeliveryThreshold (£30), deliveryFrom (£2.99), delivery methods + prices/ETAs, dispatchCutoff {16:00, workingDays, holidays}, loyaltyEarnRate. - Address lookup: provider + API key.
- Payments: Worldpay hosted-fields config; reserved wallet config (Apple domain-verification file when Apple Pay lands).
- Age verification: provider + pass/fail/inconclusive handling + ID-step provider + retention rules.
9. Still to confirm
Age-verification provider & handling · address-lookup provider & key · Worldpay integration mode (hosted fields vs redirect vs drop-in) · real Trustpilot score · loyalty earn rate · dispatch calendar · payments timeline (Apple/Google Pay; PayPal/Klarna eligibility).
10. Compliance
No "disposable" wording · 18+ throughout · no health / "safer" / quit claims · factual button copy.