Eventer
Registration, coupons, Stripe, barcode badges and CRM sync for a Swiss events operator, in one WordPress plugin. Now being rebuilt on clean foundations.
Ø1 Problem
Dagorà runs summits and community events all year. Each needs a registration form, invitation and coupon codes, payment, a front desk on the day, badges at the door, and a clean hand-off to the CRM. Off-the-shelf event plugins covered some of that badly and the rest not at all, and every event ended with someone exporting a CSV at midnight.
Ø2 Solution
One plugin, one event per site, driven by configuration. A registration goes through validation, coupon checks and payment, confirms itself, joins the right HubSpot list and receives a badge link. On the day, the front desk sees who has checked in and prints badges against the same record. Sessions, speakers and rooms publish to the public agenda from the same admin.
Ø3 Delivery
I wrote Eventer in 2022 and it has run every event since. Several years of live events showed exactly where the original design creaked, so it is being rebuilt as v8: a layered, namespaced codebase with domain objects, use cases, ports and infrastructure adapters. The new code loads alongside the legacy code and stays inert until it is ready, so nothing on a live site changes before the cutover. The legacy code was treated as a record, not a mess: eleven display templates that existed only in the database were archived before a line was rewritten. Payment is a server-side redirect to hosted Stripe Checkout, reconciled by a signature-verified webhook. Badges are PDFs stamped onto an admin-uploaded template with Code 128 barcodes.
Notes from the build
- A registration state machine that makes confirmation idempotent when the success page and the Stripe webhook race. Paid never downgrades; a repeated transition is a no-op, not an error.
- No Stripe.js and no card data near the site. The plugin only ever sees a signed webhook.
- Badge links are signed tokens carrying a registration id and an expiry. PDFs live outside the web root and are purged after sixty days.
- 150 unit tests with in-memory doubles, PHPStan level 6, and two structural guards: database access only in the persistence layer, unserialize only in the legacy migration.
- The shipped plugin references zero third-party origins. polyfill.io, CDN jQuery and an unused Stripe tag all went.