# Wallet Pass System Guide

## Overview

The Upzelo Wallet Pass System enables customers to save their loyalty cards to Apple Wallet, Google Wallet, and other digital wallet applications on their mobile devices. This guide explains how the system works, how to configure it, and best practices for implementation.

### What is a Wallet Pass?

A wallet pass is a digital version of a loyalty card that customers can save to their phone's native wallet app (Apple Wallet on iPhone, Google Wallet on Android, etc.). Instead of showing a physical card or screenshot, customers tap their phone at checkout for a seamless experience.

**Real-world example**: A customer earns 50 points. Instead of visiting your website to check their balance, they open Apple Wallet, tap their Upzelo loyalty pass, and see their updated balance and available rewards instantly.

***

## How Wallet Passes Work

### The Three Platforms

Upzelo supports wallet passes on three platforms:

| Platform          | Device              | File Format        | Setup                                          | Push Updates               |
| ----------------- | ------------------- | ------------------ | ---------------------------------------------- | -------------------------- |
| **Apple Wallet**  | iPhone, Apple Watch | `.pkpass` (signed) | Requires Apple Developer Account + Certificate | Yes (push notifications)   |
| **Google Wallet** | Android phone       | JWT token          | Google API credentials                         | Yes (push notifications)   |
| **Browser/Web**   | All devices         | HTML page          | Automatic                                      | Yes (in-app notifications) |

### Core Architecture

```
Customer → Loyalty Widget → Upzelo API → Pass Service
                                             ↓
                                      (Apple/Google APIs)
                                             ↓
                                      Signed Pass File
                                             ↓
                                      Customer's Wallet
```

### Key Components

1. **Pass Template** (`PassTemplate` model)
   * Stores branding: colors, logos, background images
   * One template per loyalty program (can have multiple if needed)
   * Properties: `bg_color`, `text_color`, `image_path`, `push_icon_path`, `ios_logo_path`, `android_logo_path`, `ios_stripe_path`, `android_hero_path`
2. **Pass Record** (`Pass` model)
   * Represents a single customer's pass
   * Tied to a specific Customer and Tenant
   * Properties: `serial`, `auth_token`, `pass_class`, `pass_object`, `platform` (apple/android)
   * Contains the actual barcode/QR code data
3. **Pass Registration** (`PassRegistration` model)
   * Tracks device registrations for push notifications
   * Apple: `device_library_identifier` + push token
   * Allows Upzelo to update pass when customer's points/rewards change
   * Properties: `push_token`, `device_library_identifier`, `is_active`
4. **Pass Service** (`PassService` class)
   * Core business logic for creating and updating passes
   * Generates signed Apple passes using certificates
   * Generates Google Wallet JWTs using service credentials
   * Handles barcode/QR generation and validation

***

## Setting Up Wallet Passes

### Prerequisites

#### For Apple Wallet

1. Apple Developer Account ($99/year)
2. Apple Pass Type ID
   * Create in: Apple Developer → Certificates, Identifiers & Profiles → Pass Type IDs
   * Example: `pass.upzelo.com`
3. Pass Signing Certificate
   * Generate CSR in Apple Developer portal
   * Use to sign passes (stored at `/storage/app/certs/pass-cert.p12`)
4. WWDR Certificate
   * Download from Apple (stored at `/storage/app/certs/wwdr.pem`)

**Upzelo config** (hardcoded in `PassService`):

```
ISSUER_ID = '3388000000022348882'
TEAM_IDENTIFIER = '54RPUWM7QW'
PASS_TYPE_IDENTIFIER = 'pass.upzelo.com'
```

#### For Google Wallet

1. Google Cloud Project
2. Google Wallet API enabled
3. Service Account credentials
   * Download JSON key
   * Used to generate JWTs for pass creation
4. Issuer ID (numeric)

**Setup reference**: Check `PassService::ANDROID` constant and `ServiceCredentials` usage

***

## How Customers Get Wallet Passes

### Step 1: Customer Initiates Pass Generation

**Entry Point**: Loyalty Widget → "Add to Wallet" button

The widget redirects to:

```
https://app.upzelo.com/wallet/{customerId}
```

This route (`PassController::generateWalletPass`) authenticates the customer and displays a mobile-friendly interface.

### Step 2: Upzelo Generates the Pass

Based on device platform (detected via User-Agent), Upzelo creates:

**For Apple**:

* Calls `PassService::applePass()`
* Uses `Chiiya\Passes\Apple\PassFactory` to build `.pkpass` file
* Signs with certificate from `/storage/app/certs/pass-cert.p12`
* Embeds barcode/QR code with customer's loyalty ID
* Returns `.pkpass` download

**For Android/Google Wallet**:

* Calls `PassService::androidPass()`
* Uses `Chiiya\Passes\Google\JWT` to create JWT token
* Calls Google Wallet API: `https://www.google.com/pay/passes/` with JWT
* Returns redirect URL to Google Wallet

**For Web/Browser**:

* Renders HTML landing page with pass details
* QR code points to: `https://app.upzelo.com/pass/{tenantId}/{customerId}`
* No installation needed

### Step 3: Device Registration (Push Updates)

When customer adds pass to wallet, device registers with Upzelo via:

**Apple Wallet Protocol** (Passbook REST API):

```
POST /iosPass/v1/devices/{deviceLibraryIdentifier}/registrations/{passTypeIdentifier}/{serialNumber}
```

This creates a `PassRegistration` record with:

* `device_library_identifier` — unique device ID
* `push_token` — for sending updates
* `is_active` — true if device is registered

**Google Wallet**:

* Device registration automatic via JWT
* Push updates via Google's FCM (Firebase Cloud Messaging)

### Step 4: Pass Updates via Push Notifications

When customer's loyalty data changes:

1. **Loyalty action occurs** (points earned, reward redeemed, tier changed)
2. **System triggers**: `LoyaltyModelUpdated` event
3. **PassService regenerates pass** with new data
4. **Push notification sent** via registered devices:
   * Apple: `PendingPushNotification` queued, sent via Passbook API
   * Google: Updates via FCM to all registered devices
5. **Customer's wallet updates** without manual refresh

***

## Workflow: Point of Sale Integration

### Scanning the Pass at Checkout

1. **Cashier scans** customer's pass (QR/barcode)
2. **QR contains**: `{tenantId}:{customerId}:{serialNumber}`
3. **System resolves** to customer's loyalty account
4. **Points/rewards applied** during checkout
5. **PassService updates pass** → Push sent to customer's devices
6. **Customer sees updated balance** in wallet (within seconds)

### Barcode/QR Code Content

The barcode embedded in the pass contains:

* Tenant ID
* Customer ID
* Serial number (unique per pass)

This is the bridge between physical scan and digital identity.

***

## Configuration

### Enable Wallet Passes for Your Loyalty Program

1. **Navigate to**: Dashboard → Loyalty Program → Settings → Mobile Experiences
2. **Toggle**: "Enable Wallet Passes"
3. **Choose**: Apple, Android, or both
4. **Configure Pass Template**:
   * Background color (hex)
   * Text color (hex)
   * Upload logo (PNG/JPG)
   * Upload background image (optional)
   * Set brand colors for cards

### Database Schema

#### `passes` table

```sql
id, tenant_id, customer_id, platform (apple/android), 
hide_instructions, serial, auth_token, 
pass_class (Apple), pass_object (Google), 
created_at, updated_at
```

#### `pass_registrations` table

```sql
id, pass_id, device_library_identifier, 
push_token, is_active, created_at, updated_at
```

#### `pass_templates` table

```sql
id, tenant_id, is_default, title, 
bg_color, text_color, image_path, push_icon_path,
ios_logo_path, android_logo_path, 
ios_stripe_path, android_hero_path,
created_at, updated_at, deleted_at
```

#### `loyalty_settings` table (additions)

```sql
passes_enabled (boolean), pass_qr_text (string)
```

***

## Troubleshooting

### Pass Won't Generate

**Symptom**: "Error generating pass" or redirect fails

**Causes**:

1. **Apple certificates missing** — Verify `/storage/app/certs/pass-cert.p12` and `/storage/app/certs/wwdr.pem` exist
2. **Google credentials invalid** — Validate service account JSON has correct scopes
3. **Pass template not set** — Loyalty program must have a pass template configured
4. **Tenant not configured** — Ensure tenant has required API keys

**Debug**: Check logs at `/storage/logs/laravel.log` for PassService errors

### Push Notifications Not Working

**Symptom**: Pass shows old points/rewards after update

**Causes**:

1. **Device not registered** — Check `pass_registrations` table for customer's devices
2. **Push token expired** — Apple tokens expire; device must re-register
3. **Pass disabled** — Check `is_active` flag in `pass_registrations`
4. **Queue not running** — `php artisan queue:work` must be running for push jobs

**Debug**: Monitor queue: `php artisan queue:monitor`

### QR Code Not Scanning

**Symptom**: Cashier can't scan barcode at point of sale

**Causes**:

1. **Pass platform incompatible** — Android phones can't scan Apple passes; ensure correct format
2. **QR code too small** — Check pass template sizing
3. **Image compression** — PNG/JPG quality degraded during upload

**Test**: Use phone's native camera app to scan QR code (not loyalty app)

***

## Best Practices

### 1. Clear Pass Design

* **High contrast** between barcode and background
* **QR code white space** — 20% margin around barcode
* **Logo placement** — Top or bottom, not overlapping barcode
* **Color accessibility** — Test with colorblind-friendly palette

### 2. Barcode Format Strategy

| Scenario                      | Format             | Why                          |
| ----------------------------- | ------------------ | ---------------------------- |
| High-volume retail (POS scan) | QR Code (Code 128) | Faster scanning, larger data |
| Customer reference            | Barcode (Code 39)  | Human-readable               |
| Subscriptions/Memberships     | Code 128           | Standard for barcodes        |

### 3. Push Notification Timing

* **Real-time**: Points awarded → instant pass update
* **Batch**: Large campaigns → queue to avoid rate limits
* **Frequency cap**: Don't push update more than once per minute per customer

### 4. Testing Before Launch

```bash
# Test Apple pass generation
curl https://app.upzelo.com/apple-pass

# Test Android pass generation
curl https://app.upzelo.com/pass?customerId=123

# Verify pass records created
mysql -u vali upzelo -e "SELECT * FROM passes LIMIT 5;"

# Check device registrations
mysql -u vali upzelo -e "SELECT * FROM pass_registrations WHERE is_active = 1;"
```

### 5. Analytics & Monitoring

Track in Loyalty Settings:

* % of customers with passes installed
* Average daily active passes (devices registered)
* Pass scan rate vs. total transactions
* Push notification delivery rate

***

## Technical Reference

### API Endpoints

| Endpoint                                                         | Method | Purpose                | Auth             |
| ---------------------------------------------------------------- | ------ | ---------------------- | ---------------- |
| `/wallet/{customerId}`                                           | GET    | Generate/download pass | Customer session |
| `/apple-pass`                                                    | GET    | Test Apple pass        | None (dev)       |
| `/pass`                                                          | GET    | Generate Google pass   | Query params     |
| `/android-pass/{tenantId}/{customerId}`                          | GET    | Android redirect       | URL params       |
| `/pass/{tenantId}/{customerId}`                                  | GET    | Web pass view          | URL params       |
| `/iosPass/v1/devices/{deviceId}/registrations/{typeId}/{serial}` | POST   | Register device        | Auth token       |
| `/iosPass/v1/devices/{deviceId}/registrations/{typeId}/{serial}` | DELETE | Unregister device      | Auth token       |

### Service Classes

**`PassService`** (`app/Services/Passes/PassService.php`)

* `applePass()` — Generate Apple `.pkpass` file
* `androidPass()` — Generate Google Wallet JWT
* `generatePassForCustomer()` — Route handler for web view
* `registerDevice()` — Apple device registration
* `unregisterDevice()` — Remove device from push list
* `getListOfUpdatablePasses()` — Fetch passes for device

**`PassController`** (`app/Http/Controllers/PassController.php`)

* Routes for pass generation
* Device registration/unregistration handlers

### Events

**`LoyaltyModelUpdated`**

* Fired when loyalty data changes (points, rewards, tiers)
* Listeners: `PassService::updatePasses()`

### Jobs (Queue)

Async pass operations:

* `SendPushNotification` — Queue push updates to devices
* `UpdatePassForCustomer` — Regenerate pass in background

***

## Production Deployment Checklist

* [ ] Apple certificates uploaded and verified
* [ ] Google Wallet API credentials configured
* [ ] Pass template created and published
* [ ] QR/barcode text configured in Loyalty Settings
* [ ] Test pass generation on iOS and Android
* [ ] Device registration tested (add pass to wallet)
* [ ] Push notification tested (update loyalty data, verify pass updates)
* [ ] POS system configured to scan QR codes
* [ ] Monitoring dashboard set up (pass metrics)
* [ ] Support team trained on troubleshooting
* [ ] Analytics events logged correctly

***

## Related Guides

* [Loyalty Program Setup](/documentation/system_inventory.md) — Configure loyalty features
* [Shopify Integration](/documentation/shopify_onboarding_guide.md) — Enable passes on Shopify stores
* [Campaigns & Offers](/documentation/system_inventory.md#campaigns) — Drive pass engagement

***

## Support

For technical support on wallet pass issues:

* **API Issues**: Contact <dev@upzelo.com>
* **Certificate Questions**: See [Apple Passbook Documentation](https://developer.apple.com/documentation/walletkit)
* **Google Wallet Issues**: See [Google Wallet Documentation](https://developers.google.com/wallet)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://guides.upzelo.xyz/documentation/wallet_pass_system_guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
