PHPackages                             reachweb/laravel-wallet-passes - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. reachweb/laravel-wallet-passes

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

reachweb/laravel-wallet-passes
==============================

Laravel package for generating Apple Wallet (.pkpass) and Google Wallet digital passes

v0.2.0(3mo ago)03MITPHPPHP ^8.3CI passing

Since Apr 10Pushed 3mo agoCompare

[ Source](https://github.com/reachweb/laravel-wallet-passes)[ Packagist](https://packagist.org/packages/reachweb/laravel-wallet-passes)[ Docs](https://github.com/reachweb/laravel-wallet-passes)[ RSS](/packages/reachweb-laravel-wallet-passes/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (14)Versions (3)Used By (0)

reach/laravel-wallet-passes
===========================

[](#reachlaravel-wallet-passes)

A Laravel package for generating, signing, and managing Apple Wallet (`.pkpass`) and Google Wallet digital passes behind a single, fluent API.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 12 or 13
- `ext-openssl` (Apple pass signing)
- `ext-zip` (Apple `.pkpass` packaging)

Installation
------------

[](#installation)

```
composer require reach/laravel-wallet-passes
```

Publish the config file:

```
php artisan vendor:publish --tag=wallet-passes-config
```

Publish and run the migrations (required for Apple pass-update web service):

```
php artisan vendor:publish --tag=wallet-passes-migrations
php artisan migrate
```

Configuration
-------------

[](#configuration)

Add the following to your `.env` file. Only configure the providers you plan to use.

### Apple Wallet

[](#apple-wallet)

```
APPLE_PASS_CERTIFICATE_PATH=/path/to/pass-certificate.p12
APPLE_PASS_CERTIFICATE_PASSWORD=your-certificate-password
APPLE_WWDR_CERTIFICATE_PATH=/path/to/AppleWWDRCAG4.pem
APPLE_PASS_TYPE_IDENTIFIER=pass.com.example.mypass
APPLE_TEAM_IDENTIFIER=ABCDE12345
APPLE_ORGANIZATION_NAME="Your Company"
APPLE_WEB_SERVICE_URL=https://example.com/wallet
APPLE_APNS_ENVIRONMENT=production
```

VariableDescription`APPLE_PASS_CERTIFICATE_PATH`Absolute path to the `.p12` Pass Type ID certificate exported from Keychain Access`APPLE_PASS_CERTIFICATE_PASSWORD`Password used when exporting the `.p12` file`APPLE_WWDR_CERTIFICATE_PATH`Absolute path to Apple's WWDR G4 intermediate certificate (`.pem`)`APPLE_PASS_TYPE_IDENTIFIER`The Pass Type ID registered in your Apple Developer account (e.g. `pass.com.example.mypass`)`APPLE_TEAM_IDENTIFIER`Your 10-character Apple Developer Team ID`APPLE_ORGANIZATION_NAME`Displayed on the pass lock screen`APPLE_WEB_SERVICE_URL`Base URL for pass-update callbacks (must match the `webServiceURL` in your passes)`APPLE_APNS_ENVIRONMENT``production` or `sandbox` (defaults to `production`)### Google Wallet

[](#google-wallet)

```
GOOGLE_WALLET_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
GOOGLE_WALLET_ISSUER_ID=3388000000012345678
GOOGLE_WALLET_APPLICATION_NAME=MyApp
```

VariableDescription`GOOGLE_WALLET_SERVICE_ACCOUNT_PATH`Absolute path to the Google Cloud service account JSON key file`GOOGLE_WALLET_ISSUER_ID`Your Google Wallet API Issuer ID (found in the Google Pay &amp; Wallet Console)`GOOGLE_WALLET_APPLICATION_NAME`Application name sent with API requests (defaults to `WalletPasses`)### Additional Config

[](#additional-config)

The published `config/wallet-passes.php` file also supports:

```
'default' => env('WALLET_PASS_PROVIDER', 'apple'), // 'apple', 'google', or 'both'

'routes' => [
    'enabled' => true,       // Enable Apple web service routes
    'prefix'  => 'wallet',   // URL prefix for all package routes
    'middleware' => [],       // Additional middleware to apply
],

'logging' => [
    'enabled' => env('WALLET_PASS_LOGGING', false),
    'channel' => env('WALLET_PASS_LOG_CHANNEL', 'stack'),
],
```

---

Usage
-----

[](#usage)

### Using the Facade

[](#using-the-facade)

The `WalletPass` facade is the main entry point. It returns pre-configured builders for each provider.

```
use Reach\WalletPasses\Facades\WalletPass;

// Get an Apple builder
$builder = WalletPass::apple();

// Get a Google builder
$builder = WalletPass::google();

// Attach a Passable model (auto-populates builder fields)
$builder = WalletPass::forModel($order)->apple();
```

### Creating an Apple Wallet Pass

[](#creating-an-apple-wallet-pass)

```
use Reach\WalletPasses\Facades\WalletPass;
use Reach\WalletPasses\Apple\Enums\PassStyle;
use Reach\WalletPasses\Apple\Enums\BarcodeFormat;

$pkpass = WalletPass::apple()
    ->setStyle(PassStyle::EventTicket)
    ->setSerialNumber('E-001')
    ->setDescription('Concert Ticket')
    ->setHeader('Live Concert')
    ->setColors(
        background: '#1E3A5F',
        foreground: '#FFFFFF',
        label: '#AABBCC',
    )
    ->setIcon(storage_path('passes/icon.png'))
    ->setLogo(storage_path('passes/logo.png'))
    ->setStrip(storage_path('passes/strip.png'))
    ->addHeaderField('date', 'Date', 'Jun 15, 2025')
    ->addPrimaryField('event', 'Event', 'Summer Music Festival')
    ->addSecondaryField('location', 'Location', 'Main Stage')
    ->addAuxiliaryField('seat', 'Seat', 'GA-042')
    ->addBackField('terms', 'Terms', 'No refunds.')
    ->setBarcode('E-001', BarcodeFormat::QR)
    ->addLocation(37.3349, -122.0090, 'Venue')
    ->setRelevantDate(new DateTime('2025-06-15T18:00:00-07:00'))
    ->setWebServiceUrl(config('wallet-passes.apple.web_service_url'))
    ->setAuthenticationToken('your-auth-token')
    ->generate(); // Returns binary .pkpass content
```

#### Returning a download response

[](#returning-a-download-response)

```
// In a controller:
return WalletPass::apple()
    ->setSerialNumber('E-001')
    // ... other fields ...
    ->download('concert-ticket.pkpass');
```

### Creating a Google Wallet Pass

[](#creating-a-google-wallet-pass)

```
use Reach\WalletPasses\Facades\WalletPass;
use Reach\WalletPasses\Apple\Enums\BarcodeFormat;

// Option 1: Generate a save URL via JWT (no server-side API call)
$saveUrl = WalletPass::google()
    ->setClassId('concert-class')
    ->setClassName('Live Concerts')
    ->setObjectId('ticket-001')
    ->setHeader('Summer Music Festival')
    ->setSubheader('Main Stage')
    ->setHeroImage('https://example.com/hero.png', 'Concert banner')
    ->setLogo('https://example.com/logo.png', 'Logo')
    ->setBackgroundColor('#1E3A5F')
    ->addField('date', 'Date', 'Jun 15, 2025')
    ->addField('seat', 'Seat', 'GA-042')
    ->addLink('https://example.com/event', 'Event Details')
    ->setBarcode('ticket-001', BarcodeFormat::QR)
    ->addLocation(37.3349, -122.0090)
    ->setState('ACTIVE')
    ->setExpiration(new DateTime('2025-06-16T00:00:00Z'))
    ->generateSaveUrl(); // Returns "Add to Google Wallet" URL

// Option 2: Create via REST API first, then generate save URL
$saveUrl = WalletPass::google()
    ->setClassId('concert-class')
    ->setObjectId('ticket-001')
    // ... other fields ...
    ->createViaApi(); // Creates class/object via API, returns save URL
```

### Model Integration with the `Passable` Interface

[](#model-integration-with-the-passable-interface)

For models that represent passes (e.g. orders, tickets, memberships), implement the `Passable` interface and use the `HasWalletPass` trait:

```
use Illuminate\Database\Eloquent\Model;
use Reach\WalletPasses\Contracts\Passable;
use Reach\WalletPasses\Traits\HasWalletPass;
use Reach\WalletPasses\DTOs\PassField;
use Reach\WalletPasses\DTOs\PassLocation;

class Order extends Model implements Passable
{
    use HasWalletPass;

    public function getPassSerialNumber(): string
    {
        return 'ORDER-' . $this->id;
    }

    public function getPassDescription(): string
    {
        return 'Order #' . $this->id;
    }

    public function getPassFields(): array
    {
        return [
            new PassField('item', 'Item', $this->item_name),
            new PassField('qty', 'Quantity', (string) $this->quantity),
        ];
    }

    public function getPassBarcodeMessage(): string
    {
        return 'ORDER-' . $this->id;
    }

    public function getPassLocations(): array
    {
        return [
            new PassLocation(
                latitude: $this->store_lat,
                longitude: $this->store_lng,
                relevantText: 'Pickup location',
            ),
        ];
    }
}
```

Then use the trait methods:

```
$order = Order::find(1);

// Generate an Apple .pkpass binary
$pkpass = $order->generateApplePass();

// Return a download response
return $order->getApplePassResponse('order-pass.pkpass');

// Generate a Google Wallet save URL
$url = $order->generateGooglePassUrl();

// Generate a signed download URL (expires in 24 hours)
$downloadUrl = $order->getPassDownloadUrl();

// Notify registered devices of a pass update (Apple)
$notified = $order->notifyPassUpdate();
```

---

Apple Wallet Setup Guide
------------------------

[](#apple-wallet-setup-guide)

### 1. Create a Pass Type ID

[](#1-create-a-pass-type-id)

1. Go to [Apple Developer &gt; Certificates, Identifiers &amp; Profiles](https://developer.apple.com/account/resources)
2. Under **Identifiers**, click **+** and select **Pass Type IDs**
3. Enter a description and an identifier (e.g. `pass.com.yourcompany.mypass`)
4. Register the identifier

### 2. Create and Export the Pass Certificate

[](#2-create-and-export-the-pass-certificate)

1. In the Identifiers list, select your Pass Type ID and click **Create Certificate**
2. Follow the prompts to create a Certificate Signing Request (CSR) via Keychain Access
3. Upload the CSR and download the certificate (`.cer` file)
4. Double-click the `.cer` to install it in Keychain Access
5. In Keychain Access, find the certificate under **My Certificates**, right-click, and select **Export**
6. Save as a `.p12` file with a password
7. Place the `.p12` file in a secure location on your server (outside the web root)
8. Set `APPLE_PASS_CERTIFICATE_PATH` and `APPLE_PASS_CERTIFICATE_PASSWORD` in `.env`

### 3. Download the WWDR Intermediate Certificate

[](#3-download-the-wwdr-intermediate-certificate)

1. Download the **Apple Worldwide Developer Relations (WWDR) G4** certificate from [Apple PKI](https://www.apple.com/certificateauthority/)
2. Convert to PEM if needed: ```
    openssl x509 -inform der -in AppleWWDRCAG4.cer -out AppleWWDRCAG4.pem
    ```
3. Place the `.pem` file alongside your `.p12` and set `APPLE_WWDR_CERTIFICATE_PATH`

### 4. Find Your Team Identifier

[](#4-find-your-team-identifier)

Your 10-character Team ID is shown at the top right of the [Apple Developer Membership](https://developer.apple.com/account#MembershipDetailsCard) page. Set it as `APPLE_TEAM_IDENTIFIER`.

---

Google Wallet Setup Guide
-------------------------

[](#google-wallet-setup-guide)

### 1. Create a Google Cloud Project

[](#1-create-a-google-cloud-project)

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one
3. Enable the **Google Wallet API** under APIs &amp; Services &gt; Library

### 2. Create a Service Account

[](#2-create-a-service-account)

1. Go to **IAM &amp; Admin &gt; Service Accounts**
2. Click **Create Service Account**
3. Give it a name and grant no specific roles (the Wallet API uses its own permissions)
4. On the service account detail page, go to **Keys &gt; Add Key &gt; Create new key**
5. Select **JSON** format and download the key file
6. Place the JSON file in a secure location and set `GOOGLE_WALLET_SERVICE_ACCOUNT_PATH`

### 3. Set Up a Wallet API Issuer Account

[](#3-set-up-a-wallet-api-issuer-account)

1. Go to the [Google Pay &amp; Wallet Console](https://pay.google.com/business/console)
2. Sign up for the Wallet API and create an Issuer account
3. Copy your **Issuer ID** (a numeric string) and set `GOOGLE_WALLET_ISSUER_ID`
4. Under **Manage &gt; API Access**, add your service account email as a user with **Developer** permissions

### 4. Demo Mode

[](#4-demo-mode)

New Wallet API issuer accounts start in **demo mode**. Passes can only be saved by users added as test users in the Google Pay &amp; Wallet Console. Apply for production access once your integration is ready.

---

Apple Web Service (Pass Updates)
--------------------------------

[](#apple-web-service-pass-updates)

This package implements Apple's [web service protocol](https://developer.apple.com/documentation/walletpasses/adding-a-web-service-to-update-passes) for pass updates. When enabled, the following endpoints are registered automatically:

MethodEndpointDescriptionPOST`{prefix}/v1/devices/{deviceId}/registrations/{passTypeId}/{serial}`Register a device for push updatesDELETE`{prefix}/v1/devices/{deviceId}/registrations/{passTypeId}/{serial}`Unregister a deviceGET`{prefix}/v1/devices/{deviceId}/registrations/{passTypeId}`List serial numbers for a deviceGET`{prefix}/v1/passes/{passTypeId}/{serial}`Get the latest version of a passPOST`{prefix}/v1/log`Receive log messages from Apple WalletGET`{prefix}/download/{serial}`Download a pass via signed URL### Implementing the PassRetriever

[](#implementing-the-passretriever)

When Apple requests the latest version of a pass, the package needs to know how to build it. Create a class implementing `PassRetriever` and register it in your config:

```
// app/Services/MyPassRetriever.php
use Reach\WalletPasses\Apple\ApplePassBuilder;
use Reach\WalletPasses\Contracts\PassRetriever;
use Reach\WalletPasses\Facades\WalletPass;

class MyPassRetriever implements PassRetriever
{
    public function retrieve(string $passTypeIdentifier, string $serialNumber): ?ApplePassBuilder
    {
        $order = Order::where('serial', $serialNumber)->first();

        if (! $order) {
            return null; // Returns 404 to Apple
        }

        return WalletPass::forModel($order)->apple()
            ->setIcon(storage_path('passes/icon.png'))
            ->setAuthenticationToken($order->pass_token);
    }
}
```

Register it in `config/wallet-passes.php`:

```
'apple' => [
    // ...
    'pass_retrieval_handler' => \App\Services\MyPassRetriever::class,
],
```

### Sending Pass Update Notifications

[](#sending-pass-update-notifications)

When a pass's data changes, notify registered devices to fetch the updated version:

```
use Reach\WalletPasses\Apple\ApplePassUpdater;

$updater = new ApplePassUpdater(config('wallet-passes.apple'));

// Notify devices registered for a specific pass
$count = $updater->notifyDevices('pass.com.example.mypass', 'ORDER-42');

// Notify all devices for a pass type
$count = $updater->notifyAllDevices('pass.com.example.mypass');
```

Or via the `HasWalletPass` trait:

```
$order->notifyPassUpdate();
```

---

Events
------

[](#events)

The package dispatches the following events. Listen for them in your `EventServiceProvider` or with `Event::listen()`:

EventPropertiesWhen`PassCreated``provider`, `serialNumber`, `passTypeIdentifier`A pass is generated`PassUpdated``provider`, `serialNumber`, `passTypeIdentifier`, `updateType`A pass is updated`PassRetrieved``serialNumber`, `deviceLibraryIdentifier`A device fetches the latest pass`DeviceRegistered``deviceLibraryIdentifier`, `serialNumber`, `pushToken`A device registers for updates`DeviceUnregistered``deviceLibraryIdentifier`, `serialNumber`A device unregistersAll events live under `Reach\WalletPasses\Events\`.

```
use Reach\WalletPasses\Events\DeviceRegistered;

Event::listen(DeviceRegistered::class, function (DeviceRegistered $event) {
    Log::info("Device {$event->deviceLibraryIdentifier} registered for pass {$event->serialNumber}");
});
```

---

Exceptions
----------

[](#exceptions)

All package exceptions extend `Reach\WalletPasses\Exceptions\WalletPassException`, so you can catch every package error with one type:

ExceptionThrown When`CertificateNotFoundException``.p12` or WWDR certificate file is missing or unreadable`PassSigningException`OpenSSL signing fails (bad cert, wrong password, etc.)`InvalidPassDataException`Required fields are missing when calling `generate()` or `generateJwt()``GoogleApiException`Google Wallet REST API returns an error```
use Reach\WalletPasses\Exceptions\WalletPassException;
use Reach\WalletPasses\Exceptions\CertificateNotFoundException;

try {
    $pkpass = WalletPass::apple()->setSerialNumber('001')->generate();
} catch (CertificateNotFoundException $e) {
    // Handle missing certificate
} catch (WalletPassException $e) {
    // Handle any other package error
}
```

---

Troubleshooting
---------------

[](#troubleshooting)

### Apple Wallet

[](#apple-wallet-1)

**"Invalid certificate" or signing errors**

- Ensure the `.p12` was exported from a Pass Type ID certificate, not a development/distribution certificate
- Verify the password matches what was set during export
- Make sure you're using the **WWDR G4** certificate (not G1/G2/G3)
- Check that `ext-openssl` is installed: `php -m | grep openssl`

**Pass doesn't appear on device**

- The icon image is required. Ensure `setIcon()` is called with a valid PNG path
- Serial number, pass type identifier, and team identifier are all required
- Check that the `.pkpass` MIME type is `application/vnd.apple.pkpass`

**Pass updates not working**

- Verify `webServiceURL` in the pass matches your `APPLE_WEB_SERVICE_URL`
- The authentication token must be at least 16 characters
- Ensure routes are enabled in config (`wallet-passes.routes.enabled`)
- Check that your `PassRetriever` implementation is registered in config
- For sandbox testing, set `APPLE_APNS_ENVIRONMENT=sandbox`

### Google Wallet

[](#google-wallet-1)

**"Permission denied" API errors**

- Ensure the service account email has **Developer** access in the Google Pay &amp; Wallet Console
- Verify the JSON key file path is correct and readable

**"Class not found" errors**

- When using `generateSaveUrl()`, the class and object are created inline via JWT -- no API call needed
- When using `createViaApi()`, the class is created first; ensure your issuer ID is correct

**Passes only visible to test users**

- New issuer accounts are in demo mode. Add test user emails in the Google Pay &amp; Wallet Console
- Apply for production access to make passes available to all users

### General

[](#general)

**"Class not found" PHP errors**

- Run `composer dump-autoload` after installation
- Ensure the service provider is auto-discovered (check `composer.json` `extra.laravel.providers`)

**Missing migrations**

- Run `php artisan vendor:publish --tag=wallet-passes-migrations` then `php artisan migrate`

---

Testing
-------

[](#testing)

The package includes a comprehensive Pest test suite. To run the tests:

```
php artisan test --compact
```

Test certificates and Google service account fixtures are generated on the fly -- no real credentials are needed to run the test suite.

License
-------

[](#license)

MIT -- see [LICENSE](LICENSE).

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance80

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

2

Last Release

105d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/043c16e10a57aa2e23f25400c12c1ef9c809ddcb369fbd79cf9c162d89fa8289?d=identicon)[reachweb](/maintainers/reachweb)

---

Top Contributors

[![afonic](https://avatars.githubusercontent.com/u/7423993?v=4)](https://github.com/afonic "afonic (9 commits)")

---

Tags

laravelpassespkpassgoogle walletapple wallet

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/reachweb-laravel-wallet-passes/health.svg)

```
[![Health](https://phpackages.com/badges/reachweb-laravel-wallet-passes/health.svg)](https://phpackages.com/packages/reachweb-laravel-wallet-passes)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
