laulamanapps/apple-passbook-laravel
Composer 安装命令:
composer require laulamanapps/apple-passbook-laravel
包简介
Generate Apple Wallet passes (PassKit) from your Laravel application
关键字:
README 文档
README
This package provides Laravel integration for the LauLamanApps Apple Passbook Package:
generate Apple Wallet passes (.pkpass files) and serve the Apple PassKit Web Service endpoints from your Laravel application.
Requirements
- PHP 8.1+
- Laravel 11.x, 12.x or 13.x
Installation
composer require laulamanapps/apple-passbook-laravel
The service provider (LauLamanApps\ApplePassbookLaravel\ApplePassbookServiceProvider) is registered automatically via Laravel package auto-discovery.
Run Tests
composer install ./bin/phpunit
Configuration
Publish the config file:
php artisan vendor:publish --tag=apple-passbook-config
This creates config/apple-passbook.php:
| Key | Default | Description |
|---|---|---|
certificate |
env('APPLE_PASSBOOK_CERTIFICATE') |
Path to your .p12 Pass Type ID certificate |
certificate_password |
env('APPLE_PASSBOOK_CERTIFICATE_PASSWORD') |
Password protecting the .p12 file |
apple_wwdr_ca |
env('APPLE_PASSBOOK_WWDR_CA') (null) |
Optional path to an Apple WWDR CA .pem; null uses the library default |
web_service.enabled |
env('APPLE_PASSBOOK_WEB_SERVICE_ENABLED', true) |
Whether the PassKit web service routes are registered |
web_service.route_prefix |
env('APPLE_PASSBOOK_WEB_SERVICE_ROUTE_PREFIX', '/v1') |
Route prefix for the web service endpoints |
Add the ENV variables to your .env file:
###> laulamanapps/apple-passbook-laravel ### APPLE_PASSBOOK_CERTIFICATE=/path/to/certificates/pass.p12 APPLE_PASSBOOK_CERTIFICATE_PASSWORD=password ###< laulamanapps/apple-passbook-laravel ###
Get Certificate
Head over to the Apple Developer Portal to get yourself a certificate to sign your passbooks with.
Export the certificate and key to a .p12 file using Keychain Access.
Generating a Pass
The package registers LauLamanApps\ApplePassbook\Build\Compiler as a singleton, configured with your certificate. Inject it anywhere:
namespace App\Http\Controllers; use LauLamanApps\ApplePassbook\Build\Compiler; use LauLamanApps\ApplePassbook\GenericPassbook; use LauLamanApps\ApplePassbook\MetaData\Barcode; use LauLamanApps\ApplePassbook\Style\BarcodeFormat; final class PassbookController { public function __construct( private readonly Compiler $compiler, ) { } public function download() { $passbook = new GenericPassbook('8j23fm3'); $passbook->setTeamIdentifier('<TeamId>'); $passbook->setPassTypeIdentifier('<PassTypeId>'); $passbook->setOrganizationName('Toy Town'); $passbook->setDescription('Toy Town Membership'); $barcode = new Barcode(); $barcode->setFormat(BarcodeFormat::Pdf417); $barcode->setMessage('123456789'); $passbook->setBarcode($barcode); return response($this->compiler->compile($passbook), 200, [ 'Content-Description' => 'File Transfer', 'Content-Type' => 'application/vnd.apple.pkpass', 'Content-Disposition' => 'filename="passbook.pkpass"', ]); } }
See the core package documentation for all pass types (event ticket, coupon, boarding pass, store card, generic), fields, images, colors and locations.
PassKit Web Service
When web_service.enabled is true the package registers all Apple PassKit Web Service endpoints under the configured prefix (default /v1):
| Method | URI | Purpose |
|---|---|---|
POST |
/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber} |
Register a device for pass updates |
DELETE |
/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber} |
Unregister a device |
GET |
/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier} |
Serial numbers of updated passes |
GET |
/v1/passes/{passTypeIdentifier}/{serialNumber} |
Retrieve the latest version of a pass |
POST |
/v1/log |
Device diagnostic logging |
For the web service to work, the pass itself must point at your application:
$passbook->setWebService('https://example.com/', $authenticationToken);
Events
The controllers delegate all persistence decisions to your application through mutable event objects. Each event starts with Status::Unhandled — your listener must set the appropriate status, otherwise a LogicException is thrown.
| Event | Dispatched when |
|---|---|
DeviceRegisteredEvent |
A device registers for pass updates |
DeviceUnregisteredEvent |
A device unregisters from pass updates |
DeviceRequestUpdatedPassesEvent |
A device requests the serial numbers of updated passes (passesUpdatedSince is available via getPassesUpdatedSince()) |
RetrieveUpdatedPassbookEvent |
A device requests an updated pass (If-Modified-Since is available via getUpdatedSince()) |
The /v1/log endpoint needs no listener: messages are written to the application logger at info level.
Authenticating requests
Devices authenticate with an Authorization: ApplePass <token> header carrying the token you set on the pass with setWebService().
The token-carrying events expose isAuthenticatedBy(string $expectedToken): bool.
Always use
$event->isAuthenticatedBy(...)— never compare$event->getAuthenticationToken()with!==.isAuthenticatedBy()uses PHP'shash_equals(), which compares the two tokens in constant time. A plain string comparison short-circuits on the first differing byte, which leaks timing information an attacker can use to reconstruct a valid token byte by byte.
Example listeners
namespace App\Listeners; use App\Models\Pass; use DateTimeImmutable; use LauLamanApps\ApplePassbookLaravel\Events\DeviceRegisteredEvent; final class RegisterDevice { public function handle(DeviceRegisteredEvent $event): void { $pass = Pass::where('serial_number', $event->getSerialNumber())->first(); if ($pass === null || !$event->isAuthenticatedBy($pass->getAuthToken())) { $event->notAuthorized(); return; } $registration = $pass->registrations()->firstOrCreate( ['device_library_identifier' => $event->getDeviceLibraryIdentifier()], ['push_token' => $event->getPushToken()], ); if (!$registration->wasRecentlyCreated) { $event->alreadyRegistered(); return; } $event->deviceRegistered(); } }
namespace App\Listeners; use App\Models\Pass; use LauLamanApps\ApplePassbookLaravel\Events\DeviceUnregisteredEvent; final class UnregisterDevice { public function handle(DeviceUnregisteredEvent $event): void { $pass = Pass::where('serial_number', $event->getSerialNumber())->first(); if ($pass === null || !$event->isAuthenticatedBy($pass->getAuthToken())) { $event->notAuthorized(); return; } $pass->registrations() ->where('device_library_identifier', $event->getDeviceLibraryIdentifier()) ->delete(); $event->deviceUnregistered(); } }
namespace App\Listeners; use App\Models\Pass; use LauLamanApps\ApplePassbookLaravel\Events\DeviceRequestUpdatedPassesEvent; final class ListUpdatedPasses { public function handle(DeviceRequestUpdatedPassesEvent $event): void { $passes = Pass::registeredTo($event->getDeviceLibraryIdentifier()) ->where('pass_type_identifier', $event->getPassTypeIdentifier()) ->when($event->getPassesUpdatedSince(), fn ($query, $since) => $query->where('updated_at', '>', $since)) ->get(); if ($passes->isEmpty()) { $event->notFound(); return; } $event->setSerialNumbers( $passes->pluck('serial_number')->all(), $passes->max('updated_at')->toDateTimeImmutable(), ); } }
namespace App\Listeners; use App\Models\Pass; use LauLamanApps\ApplePassbookLaravel\Events\RetrieveUpdatedPassbookEvent; final class RetrieveUpdatedPassbook { public function handle(RetrieveUpdatedPassbookEvent $event): void { $pass = Pass::where('serial_number', $event->getSerialNumber())->first(); if ($pass === null) { $event->notFound(); return; } if (!$event->isAuthenticatedBy($pass->getAuthToken())) { $event->notAuthorized(); return; } $updatedAt = $pass->updated_at->toDateTimeImmutable(); if ($event->getUpdatedSince() !== null && $updatedAt <= $event->getUpdatedSince()) { $event->notModified(); return; } $event->setPassbook($pass->toPassbook(), $updatedAt); } }
Since Laravel 11, listeners in app/Listeners are auto-discovered — no manual registration is required.
The compiled pass is returned to the device as application/vnd.apple.pkpass with a Last-Modified header, and 304 Not Modified is honored via the If-Modified-Since request header.
Credits
This package has been developed by LauLaman.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 1
- 推荐数: 2
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-08