mustafa-azmi / laravel-checkin
Signed, single-use, time-limited check-in tokens for any Eloquent model — event attendance, gyms, coworking, deliveries. QR-agnostic: pair with any QR/barcode renderer you like.
Requires
- php: ^8.2
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-08-14 13:14:30 UTC
README
Signed, time-limited, single-use check-in tokens for any Eloquent model — event attendance, classes, gym entry, coworking access, deliveries. QR-agnostic by design: pair it with whatever QR/barcode renderer you like, or don't use QR at all.
Originally generalized from a university attendance system (ScanTrack) into a package that works for any "someone needs to prove they showed up" use case.
Table of contents
Try it live
A full demo app is deployed and running — create an event, issue a real signed pass, scan it with your camera or type it manually, and see every outcome (success, already-used, expired, invalid) play out for real.
laravel-checkin.alwaysdata.net →
Demo source: laravel-checkin-demo
Why this exists
Most check-in / attendance features get hand-rolled per project, with the same recurring mistakes:
- storing raw tokens that can be screenshotted or copied and reused indefinitely
- no expiry, so a leaked code works forever
- no protection against two people scanning/submitting the same code at the same instant and both being marked present
This package solves all three, and attaches to any model with one trait — verified with automated tests across PHP 8.2/8.3/8.4 and Laravel 10/11/12/13 on every push (see the badge above).
Installation
composer require mustafa-azmi/laravel-checkin
php artisan vendor:publish --tag=checkin-migrations
php artisan vendor:publish --tag=checkin-config # optional
php artisan migrate
Usage
Add the trait to any model that should be "checkinable":
use MustafaAzmi\Checkin\Traits\HasCheckins; class Event extends Model { use HasCheckins; }
Generate a token (e.g. when an attendee registers). This package doesn't render QR codes itself — pick any renderer you like and hand it the payload:
$generated = $event->generateCheckinToken(user: $attendee); // $generated->raw is the ONLY time you'll ever see the raw token — // it is never stored. Put it into a QR code with, e.g. endroid/qr-code: $qrCode = \Endroid\QrCode\QrCode::create($generated->toQrPayload());
At the door, redeem the scanned/submitted token:
use MustafaAzmi\Checkin\Facades\Checkin; use MustafaAzmi\Checkin\Exceptions\{ TokenNotFoundException, TokenExpiredException, TokenAlreadyUsedException, }; try { $token = Checkin::redeem($scannedValue); // ✅ checked in — $token->tokenable is the Event, $token->user_id is the attendee } catch (TokenNotFoundException) { // not a valid code } catch (TokenExpiredException) { // expired — code was valid but too old } catch (TokenAlreadyUsedException) { // already checked in — flag as a possible duplicate/fraud attempt }
Want to preview a code without consuming it (e.g. show attendee details on a
screen before staff confirms)? Use validate() instead of redeem() —
identical checks, but doesn't mark the token as used.
Design notes
- Tokens are HMAC-hashed, never stored raw. A full database leak cannot be used to forge or replay check-ins without also having
APP_KEY. - Redemption is wrapped in a locked transaction (
lockForUpdate), so concurrent scans of the same single-use code cannot both succeed — seetests/CheckinTokenServiceTest.phpfor the test proving this. - Polymorphic by design — one
checkin_tokenstable serves every model in your app that adopts the trait, rather than a bespoke table per feature. single_useis configurable per-token, not just globally — so the same package handles both "one-time event ticket" and "reusable gym door pass" patterns.- QR-agnostic — the package hands you a signed payload string; how you deliver it (QR image, barcode, plain text link, NFC) is entirely up to you.
Testing
composer install vendor/bin/pest
CI runs the full suite against every combination of PHP 8.2/8.3/8.4 and Laravel 10.x/11.x/12.x/13.x on every push — see .github/workflows/tests.yml.
License
MIT — see LICENSE.md.