We built DEV3 AI Events & Tickets as a WordPress plugin in PHP with a React admin and small, server-rendered front-end views. Events, venues and organisers are custom post types; occurrences, tickets, orders, attendees and check-ins live in their own tables.
Key design decisions
- No ticketing libraries. QR codes come from a built-in encoder (byte mode, error correction level M, versions 1 to 10) that renders SVG on screen and vector rectangles in the PDF. PDF tickets come from a small PDF 1.4 writer with the core Helvetica fonts, one ticket per page.
- Seats that can't be sold twice. Reserving takes a MySQL named lock per date and re-checks capacity inside it. A unique index on date and seat is the last line of defence against a concurrent insert.
- Check-in as one conditional update. A scan admits a ticket only if
checked_in_at IS NULL and the ticket is valid, so two phones scanning the same QR at the same moment can't both say "welcome". Every attempt is logged, and undo is one tap.
- Scanner in the browser. The check-in screen uses the native
BarcodeDetector where it exists and loads a jsQR fallback chunk only when it doesn't, with manual code entry for anything a camera can't read.
- Recurring dates in the event timezone. Daily, weekly, monthly (by date or nth weekday) and yearly rules are generated in the event's own timezone so start times survive DST, and dates that already have attendees are never silently deleted when a rule changes.
- AI drafts only. The event page writer, FAQ, social posts and recap email are reviewed in a diff and saved only when accepted. AI never runs on the storefront, never sends email and never changes tickets.
Architecture
Paid tickets go through WooCommerce with one hidden virtual product (classic and block checkout, HPOS), free tickets confirm at once as RSVPs, and pay-at-the-door and box-office orders are optional. Unpaid holds expire after 15 minutes by default. Calendar, list and map views are blocks and shortcodes, the map uses Leaflet and OpenStreetMap, and every event page gets JSON-LD Event schema with offers and an FAQPage when there is an FAQ. Door staff get an "Event check-in staff" role that can open the scanner and nothing else. The API client, settings and usage logs come from a shared core used across DEV3's plugins.