{ "name": "Front of House for Event Espresso", "slug": "eea-foh", "version": "0.22.1", "download_url": "https://gitea.mulligan.casa/ee-extensions/foh-updates/raw/branch/main/eea-foh/eea-foh-0.22.1.zip", "homepage": "https://gitea.mulligan.casa/ee-extensions/eea-foh", "requires": "6.4", "tested": "7.0", "requires_php": "8.1", "last_updated": "2026-06-23 18:38:00", "icons": { "1x": "https://gitea.mulligan.casa/ee-extensions/foh-updates/raw/branch/main/eea-foh/icon-128x128.png", "2x": "https://gitea.mulligan.casa/ee-extensions/foh-updates/raw/branch/main/eea-foh/icon-256x256.png" }, "author": "Mike Mulligan", "author_homepage": "https://gitea.mulligan.casa/mmulligan", "tags": [ "event espresso", "point of sale", "box office", "front of house", "ticketing", "registration" ], "sections": { "description": "
Front-of-house event check-in for Event Espresso 4/5: tablet check-in stations
\n(installable PWA, zero WP chrome), device trust with admin approval handshake,
\nclerk attribution via badge codes, and a full append-only audit trail — plus a
\nwp-admin \"default mode\" page that makes it a drop-in replacement for the official
\n(stalled) EE Barcode Scanner add-on.
Design contract: docs/SPEC.md. Read it before touching code.
\nPre-release scaffold (0.1.0). Schema and module skeleton in place; see the spec for
\nthe build plan.
eea-ticketing (enables camera/QR scanning of printed tickets)Copy the plugin directory into wp-content/plugins/ — built front-end assets are
\ncommitted under assets/app/; nothing to build or install on the host. Activate
\nper-site. Deactivate eea-barcode-scanner first; this replaces it.
Support/Updater.php removed + its instantiation). ItUpdate URI header pointing at its foh-updates channel manifest#manifest-updater.json trigger, so the network-active ManifestSchema::install, then proactively reactivated them on every admin/cron load.The lifecycle is now use-driven, matching how EE treats every other payment
\n method: the Return/Settlement rows are created/reactivated lazily on first actual
\n use (paymentMethodId()), which only happens on the site that records a
\n return/settlement — so no stray rows are planted elsewhere. The eager Schema::install
\n activation and the proactive reactivation are removed; the money path is unchanged
\n (it already self-heals the row on use). The PMT types are still registered on
\n every load (that is what stops EE deactivating the real active row on the desk site).
\n The stale notice is now purged through EE's own notice collection (the
\n...PersistentAdminNoticeManager__registerNotices seam), which works in any
\n admin/network-admin context and on any site — fixing the multisite mis-targeting of
\n the previous attempts. New EePersistentNotice::suppressAutoDeactivated().
base + surcharge; the server records the gateway-verified captured amount andLIN_code = foh-srcg-{ref}, idempotent oncard_surcharge_mismatch);eea_foh_register_payment_methods. FOH no longer carries acard built-in or reads eea_foh_collect_enabled. recordPaymentcash_refunds_enabled forward-migrated and dropped (EEA_FOH_SCHEMA_VERSIONpayment_methods.cash.options.allow_refunds (idempotent, gated on the pre-upgradePersistentAdminNoticeManager loads everyee_pers_admin_notices option on shutdown — so the prior fix's rawupdate_option() delete was overwritten every request. The notice is nowdismissNotice(..., purge: true)),Support/EePersistentNotice helper centralises the purge (incl. thesanitize_key() collection-id vs raw option-key gotcha). Cosmetic-only,capability ∩ policy ∩\n authority: PaymentsService::availableMethods / methodDescriptors now alsoDEV_capabilities column on the devices table (EEA_FOH_SCHEMA_VERSIONdbDelta upgrade on nextcan_take_payment / can_cash_refund caps stay therecordPayment and EE payment-method['cash','check','card'] list: cash and check are built-in,eea_foh_register_payment_methods filter (card is the first such method, bridged inPRO_scope.payment_methods = { inherit, methods[] }).PaymentsService::availableMethods / methodDescriptors / resolvePaymentMethod nowrecordPayment(slug) and EE payment-method resolution semantics are preserved, andcan_take_payment / can_cash_refund) remain the authority.payment_methods.cash.options.allow_refunds.cash_refunds_enabled andeea_foh_collect_enabled options stay readable; the newpayment_methods structure is derived on read from them (plus registry defaults)SellService::createSaleeea_foh_scope_ticketsPMD_ID=0 onto a live money row (mirrors the settlement guard).reverseCarrierPayment returns a hardCustomerCreditFlow call sitesPOST /station/incident returns recorded:false when thetrue).isAppBusy now also blocks on an open.incident-modal-overlay so a pending \"Card charged — NOT recorded\" dialog can't be dismissedeea-foh- cache prefix — caches.keys() is per-origin, so the prioreea-ec-* offline caches ontendered arg on apply-payment and the incidentkind validated at the schema layer); thecheck descriptor + the client check-number prompt were already wired; this makes Check available.card paymentapplyPaymentAuditRepo::log() now returns a status and error_logs the action + DBeea-checkin/v1 → eea-foh/v1 so the never-cache-API guardee-extensions org.Clerk badge printing + scan-to-login (camera or wedge). No schema change (v16).
\nGET /station/clerk-badges. (Clerk codes are attribution codes, not secrets — treat a.modal-overlay it's also immune to the focus-reclaim issue by construction..modal-overlay as a blocking overlay, not the Settings drawer (.drawer-backdrop)<select> (or the tear-feedisModalOpen() now recognizes theBluetooth thermal receipt printing, templated header/footer, itemized receipts, and
\nloud failure handling for the card-present path. No schema change (v16).
{token}POST /station/incidentescpos.ts) is now generic primitives + a composeDocument frame;FOH returns, refunds & customer credit — the v1.0.0-blocking foundation. Full design
\nin docs/RETURNS-CREDIT-IMPLAN.md.
EE_Transaction (POST /station/exchange): replacement items are real ticketEE_PMT_Return{ticket_id, option_id, qty}), and the server computes the net. Restock fires oncan_cash_refund role cap and an event-wide \"cash refundscan_cash_refund cap + Settlement method).PAY_amount stays the applied charge; the tendered and change are recorded on theEE_Payment (PAY_extra_accntng human-readable + PAY_details structured cents) and shownredirect-kind payment-method descriptor carries its own launch template + returnrecord_method, so the PWA drives the deep-link launch and the/checkin/payment-result return entirely from the descriptor — zero provider code ineea-checkin-collect addon plugin (separate, scoped): registers the card method viaeea_checkin_payment_methods with the device-confirmed Collect-for-Stripe contractstatus/chargeId/finalAmount, GET) and owns its own enable toggle via the neweea_checkin_settings_form_extra / eea_checkin_settings_saved hooks. A future SquarePOST /station/email-receipt fires EE's own EED_Messages::payment()assetlinks.json needed).eea_checkin_payment_methods hardener now preserves record_method/launch/returnmodes includes sell ∩ clerkcan_take_payment): a Square-style item grid (category tabs derived from ticketPOST /station/create-sale →SellService + SellController with GET /station/ticket-catalog,GET /station/ticket-questions, POST /station/create-sale. create-sale builds thesystem_question_to_attendee_field_mapping(). Sold-out / sale-ended tickets are showneea_checkin_registration_questions filter,-cond) applies in Sell exactly as in SPCO. Non-inputmonetary/pricing) are excluded.eea-checkin no longer knows about ticket classes (a concept added by theeea-ticket-classes plugin). The profile \"Ticket Class Filter\" (PCRE) isPRO_scope.tickets; none = all), andeea_checkin_scope_tickets, eea_checkin_ticket_catalog,eea_checkin_profile_form_extra, eea_checkin_profile_saved. eea-ticket-classes nowROL_can_assist lets a permittedPOST /station/assist-login + /station/assist-exitSES_kind/SES_parent_id); assist-mode chrome = a contrasting viewport border, ancan_assist and the assisting clerk's stationassist_invalidates_other_devices (default off) ends thedata-themeeea_checkin_registration_answers hook) and a:focus-visible rings, full prefers-reduced-motion support.modes allow-list ∩ clerk capability (schema v12;can_take_payment).eea_checkin_payment_methods filter producing hardened descriptors{slug,label,kind,available,ordering,capture?}; the client dispatches by kindinstant cash/check, redirect card). New method = new descriptor, no core change.POST /station/apply-payment /GET …/payment-info: take cash/check now; Collect card is scaffolded (descriptor +/checkin/payment-result shell route) but available:false behind theeea_checkin_collect_enabled option until the M2 device round-trip is confirmed.POST /station/complete-transaction: finalizes an incomplete, zero-balanceupdate_status_based_on_total_paid() +update_transaction_and_registrations_after_checkout_or_payment() (guarded, idempotent).EE_Registration::answers() →EE_Answer::pretty_value()) behind the eea_checkin_registration_answers filterbalance/complete.PRO_checkin_statusesRAP = approved-only). Drives the search status-filter; thetxn_status + txn_complete; the check-in payment gate and the Take-Payment affordancetxn_id > 0 && !txn_complete (an incomplete transaction needs handling even at a:focus-visible rings, and comprehensive prefers-reduced-motion support.PaymentsService::recordPayment): records an EE_PaymentEE_Payment_Processor::update_txn_based_on_payment — appliesPOST /station/apply-payment + GET /station/transaction/{id}/payment-infocan_take_payment (schema v11; manager seeded on, clerk off).TCM/paid,cash/check resolve to EE's offline methods; card→Stripe_Onsite (live only).ROL_can_view_details,403 otherwise),manager gets it; clerk does not.inputmode=\"none\") — thehandleClerkLoggedIn wasn't carrying the rolecan_override may lookup / check-in / check-out / undo on any station, regardlessActor::allows() bypass). The clerk-login payloadcan_override, and the station shows the full action set (incl. Check Out /eea_checkin_reg_links):WHERE TXN_ID = ?, per-profile \"Surface transaction siblings\" toggle); thewp eea-checkin import-reg-links <csv> [--relation] [--replace] (refs accept REG_ID oreea_checkin_search_results filter (the \"results mutator\") + ResultCard.linkedflag:'caution' (e.g. a meal with no admission).eea_checkin_search_row filter$card, EE_Registration $reg, ['dtt_id'=>int]) lets a site/add-on push$card['extra'] ([{label, value, tone?}], hardenednormalizeExtra()); the station renders them as compact chips under eachexamples/roundup-2026-enrichment.php, a standaloned:N decode) on the row.0/blank = never (a no-op, excluded). A negative's-30 = logout on focus-loss AND a-0 = focus-loss logout only. Resolved shape is {ztol, minutes}, so0000-00-00 00:00:00 (the edit path coerced null through a %sec_profile_expired locked theupdate() now writes real NULL; reads normalize any zero-date to nullexecCommand('copy') with a Clipboard-APIec_clerk_not_authorized, auditedclerk_denied); enforced at clerk-login and re-checked per request. Role + clerkeea_checkin_roles, schema v7): per-role timeout, multi-loginclerk (60 min) and manager (2 min, can-override). Clerks remain customeea_checkin_clerk_sessions): clerk tokens are now v2,N minutes, 0 = never, negative (incl. -0) =never mode skips it;clerk-heartbeat endpointPRO_clerk_timeout is relabeledmanager role.ec_clerk_bumped (session force-ended) and ec_clerk_expired (idle/session first, a leaked low-value token + a glimpsedec_bad_register_nonce and no secret.X-EEA-Checkin-Secret; an enroll token can no longer even read a live station'sec_secret_required).setSecretHash() repo method — resetting a key now goes throughrequireReenroll(), which clears the nonce and returns the device to Pending so a/checkin/#e=<token>). Deleting a link never breaks already-register-device returns a one-time register_nonceUPDATE … WHERE DEV_secret_hash IS NULL), so a racing#e= enrollment links, captures the nonce and secret, andec_bad_enroll,ec_secret_required, unbound-device, and the dead #t= link.ec_validation query-too-short, ec_out_of_scope, ec_not_found). TheignoreSearch removed, no-cache on sw.js).navigator.storage.persist() at boot.ec_token_rotated (detected via previous-hashec_profile_revoked, ec_profile_expired, ec_profile_not_started —ec_bad_token.DEV_secret_hash, PRO_token_hash_prev), reactivation-free.TKT_classattendee, merch=apparel-, meals=meal-);current mode resolves the active datetime by clock —Name, CODE, manager lines) andload-{page} hooks (proper redirect-after-POST).DEV_profile, CLK_role) via reactivation-free migrations.eea-checkin/v1), ranked 5-field search, explicit-direction check-in