Dynamic Google Wallet Ticket
August 7, 2025 · AI Conversations
Currently i using
### ✅ The **object values** (like `eventName`, `venue`, etc.) override **class values** only if:
1. **The class already exists**, and
2. **The object is created with a different value than what's set in the class**, and
3. **You are creating a **new object** (or updating it if already created)**.
If you're still seeing placeholder text like `event_venue1` or `event_venue2`, **it likely means your object is not being created or updated properly**, or you’re only creating the class and not overriding it at the object level.
—
### ✅ Steps to fix:
#### ✅ 1. **Make sure you are using a unique object ID per ticket (order)**
Your code seems good here:
$objectId = $issuerId . '.' . $classId . '_' . $order_code;
✅ Make sure `$order_code` is dynamic and unique per ticket.
—
#### ✅ 2. **Use dynamic values in the object, not in the class**
You are correctly assigning dynamic data:
'eventName' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue']
]
],
'venue' => [
'name' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue']
]
],
'address' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $venue_address
]
]
],
But if the same object ID is already saved in a wallet, and you’re reusing the same one, Google will **not** override the values. The wallet user will continue to see cached/static values.
—
### 🔁 Solutions
#### ✅ Option 1: Always create a **new object ID** for every ticket
This ensures the latest data is always passed:
$objectId = $issuerId . '.' . $classId . '_' . uniqid($order_code . '_');
#### ✅ Option 2: If object already exists, you must **explicitly PATCH** the object using Google Wallet REST API
You can use their API to update existing eventTicketObjects:
– Endpoint: `https://walletobjects.googleapis.com/walletobjects/v1/eventTicketObject/{objectId}`
– Use `PATCH` and set updated `eventName`, `venue`, etc.
– Requires OAuth 2.0 service account access (JWT cannot update existing objects).
—
### ✅ Debug tip: Verify what’s inside the JWT
Use [https://jwt.io/](https://jwt.io/) and paste the generated JWT to check if:
– The `eventTicketObjects` payload contains your custom venue and event name.
– If yes, your object is fine — the issue is object caching or re-use.
—
### ✅ Final check: Are you creating the class again on every run?
Avoid recreating the class if it already exists. If you recreate it with default values (`event_venue1`, etc.), they will show up in the wallet if the object doesn’t override it properly.
—
### ✅ Summary Fix:
– ✅ Ensure `objectId` is unique for each ticket.
– ✅ Confirm `eventName` and `venue` are passed at **object level**, not just class.
– ✅ Use JWT to create new objects. Use REST API to update old ones.
– ✅ Check JWT contents to verify dynamic values.
—
Would you like help adding the logic to check if the object already exists and update it via REST API if needed?
Here’s what’s likely going wrong:
—
### 🛑 Problem:
You're **only creating the object**, assuming the class already exists (`classId = live_fossetts_ticket`) — but **the class itself probably has static placeholder values**, and you're **not overriding them with object-level data correctly**.
—
### ✅ Solution:
You **must create the class dynamically first** (or update it once with generic values), and **ensure the dynamic values like name/venue are defined only in the `object`**, not in the class.
Google Wallet uses the class for defaults. If the object doesn't override a field, the class value will be shown.
—
### ✅ Recommended Fix — Create Dynamic Class Once, or Rely on Object Data Only
#### Step-by-Step Fix:
—
#### ✅ Step 1: Check your current class in Google Wallet console
If your class ID `live_fossetts_ticket` has static values like:
– eventName: `Sample Event`
– venue: `Sample Venue`
Then unless you **override these in the object**, those will show in the wallet.
—
#### ✅ Step 2: Modify your object payload to ensure dynamic `eventName` and `venue` are correct
✅ Your current object code seems correct, but let's recheck your payload:
'eventName' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_name'] // <-- should be dynamic
]
],
'venue' => [
'name' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue'] // <-- dynamic
]
],
'address' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $venue_address
]
]
],
Make sure:
– `$order['event_name']` is not empty
– These are not `null`, or Google will fallback to class defaults
—
#### ✅ Step 3: If class is static, rename it or create dynamically per event type
You can do something like:
$classId = 'live_fossetts_ticket_' . $order['event_id']; // dynamic class per event
$fullClassId = $issuerId . '.' . $classId;
And make a `POST` call (once) to create the class with minimal/default values.
Then use this class for that event’s objects.
—
#### ✅ Step 4: Final Debug Step
Use this snippet **just before generating the JWT**:
echo '<pre>';
print_r($passObject);
echo '</pre>';
exit;
Check if it contains the correct `eventName` and `venue` values. If yes — the problem is Google Wallet caching or defaulting to class-level values.
—
### ✅ Optional: Create Class Dynamically
If you want, I can give you code to create a class dynamically via REST or JWT — so you don’t rely on static placeholder data.
Let me know, and I’ll provide it.
—
## ✅ Step-by-step: Create Event Ticket Class Dynamically (via JWT)
### 🧠 Overview:
Each event should ideally have its **own class**, e.g. `live_fossetts_ticket_1234`, so it reflects the real event name, venue, and times. The **objects** will then represent individual tickets under that class.
—
## ✅ Step 1: Build dynamic Class ID
Update your code to make `classId` unique per event:
$classId = 'live_fossetts_ticket_' . $order['event_id'];
$fullClassId = $issuerId . '.' . $classId;
—
## ✅ Step 2: Build Class Payload (with dynamic venue, time, etc.)
Before building the object, check if the class exists or just assume it's new and create this payload:
$classPayload = [
'id' => $fullClassId,
'issuerName' => 'Fossetts Circus',
'reviewStatus' => 'UNDER_REVIEW', // required
'eventName' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_name']
]
],
'venue' => [
'name' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue']
]
],
'address' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $venue_address
]
]
],
'dateTime' => [
'start' => $startDateTime,
'end' => $endDateTime
],
'hexBackgroundColor' => '#4285f4',
'logo' => [
'sourceUri' => [
'uri' => 'https://yourdomain.com/logo.png',
'description' => 'Fossetts Logo'
]
]
];
—
## ✅ Step 3: Encode JWT for class creation
You can **embed the class payload** in the same JWT:
$payload = [
'iss' => $clientEmail,
'aud' => 'google',
'typ' => 'savetowallet',
'iat' => time(),
'payload' => [
'eventTicketClasses' => [ $classPayload ], // 👈 add this line
'eventTicketObjects' => [ $passObject ]
]
];
Then encode as before:
$jwt = JWT::encode($payload, $privateKey, 'RS256');
$link = 'https://pay.google.com/gp/v/save/' . $jwt;
header('Location: ' . $link);
exit;
—
## ✅ Optional: Add logic to only create class once per event
Google returns an error if the class ID already exists. So if needed, use the Wallet REST API with service account to **check first**:
GET https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass/issuerId.classId
But for most simple cases, you can **just let the class be created once**, and avoid duplicates by using:
$classId = 'live_fossetts_ticket_' . $order['event_id'];
—
## ✅ Result
Each event gets its own Wallet class with the real:
– Event name
– Venue
– Showtime
– Logo
And the ticket (object) inherits it, while still allowing per-ticket overrides like:
– Seat number
– QR code
– Ticket holder name
—
Would you like me to give you a **final cleaned-up full version** of your code with dynamic class + object in one go?
—
## ✅ Recommended Approach
You should:
– ✅ **Create the class only once per event** — _not per ticket_.
– ✅ **Reuse that class** for all ticket objects for that specific event.
– ✅ Let each ticket (object) carry dynamic data (like customer name, barcode, seat info).
– ✅ Store the `classId` per event in your database so you don’t recreate it.
—
## ✅ One-time Class Creation Logic
You’ll need logic like:
// Build a reusable class ID per event
$eventId = $order['event_id'];
$classId = 'live_fossetts_ticket_' . $eventId;
$fullClassId = $issuerId . '.' . $classId;
// Check if class already exists (use cURL with service account token)
$classCheckUrl = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass/$fullClassId";
### 🔐 Generate Google API access token
You’ll need to generate a short-lived access token using your service account:
use GoogleAuthOAuth2;
// Generate OAuth2 token
$oauth = new OAuth2([
'audience' => 'https://oauth2.googleapis.com/token',
'issuer' => $clientEmail,
'signingAlgorithm' => 'RS256',
'signingKey' => $privateKey,
'tokenCredentialUri' => 'https://oauth2.googleapis.com/token',
'scope' => 'https://www.googleapis.com/auth/wallet_object.issuer',
]);
$authToken = $oauth->fetchAuthToken();
$accessToken = $authToken['access_token'];
—
### ✅ Check if class exists
$ch = curl_init($classCheckUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $accessToken"
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 404) {
// Create class dynamically
$classPayload = [
'id' => $fullClassId,
'issuerName' => 'Fossetts Circus',
'reviewStatus' => 'UNDER_REVIEW',
'eventName' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_name']
]
],
'venue' => [
'name' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue']
]
],
'address' => [
'defaultValue' => [
'language' => 'en-US',
'value' => $order['event_venue']
]
]
],
'dateTime' => [
'start' => $startDateTime,
'end' => $endDateTime
],
'hexBackgroundColor' => '#4285f4',
];
// Create class
$ch = curl_init('https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
"Authorization: Bearer $accessToken"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($classPayload));
$createResponse = curl_exec($ch);
$createCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($createCode !== 200) {
echo "Failed to create class: $createResponse";
exit;
}
}
—
## ✅ Now create your ticket object (same as before)
$objectId = $issuerId . '.' . $classId . '_' . $order_code;
And build `$passObject` dynamically with:
– `ticketHolderName`
– `barcode`
– `seatInfo`
– `textModulesData`
Then generate JWT with just the object:
$payload = [
'iss' => $clientEmail,
'aud' => 'google',
'typ' => 'savetowallet',
'iat' => time(),
'payload' => [
'eventTicketObjects' => [ $passObject ]
]
];
—
## ✅ Summary
| Step | Action |
|——|——–|
| 1. | Create one `eventTicketClass` per event (e.g. once for Event ID 123). |
| 2. | Store/track that `classId` in your database (`live_fossetts_ticket_123`). |
| 3. | For every new **purchase**, create a new object using that class. |
| 4. | Do **not** recreate class per order/ticket. |
| 5. | Use `ticketHolderName`, `barcode`, etc., in the **object**, not class. |
—
Would you like a full working version combining this class-check logic and your existing ticket code?
– You're using `reviewStatus: 'UNDER_REVIEW'` or `'DRAFT'`, and
– You're **issuing passes only to users directly via your app or site** (not listing publicly in Google Wallet search or gallery)
—
### ✅ TL;DR:
– ✅ You can **create and use the class instantly**.
– ⏳ **No manual approval or wait time is needed** unless you're trying to list your pass publicly.
– 🔐 You can still distribute passes via link (`https://pay.google.com/gp/v/save/…`) immediately after creating the class.
—
### 📘 Official Context
Google defines the `reviewStatus` values as:
| Status | Purpose |
|—————-|————————————————————————-|
| `DRAFT` | For development or staging; not visible to users |
| `UNDER_REVIEW` | Used during testing and development; passes are still usable |
| `APPROVED` | For production-level classes, especially if you want to go public |
> ✅ Even with `UNDER_REVIEW`, **you can issue passes and users can save them**.
You only need to request **manual approval from Google** if:
– You want your class listed **publicly** in Google Wallet discovery, or
– You're planning **mass distribution** (millions of passes),
– Or you're using features restricted to verified issuers.
—
### ✅ Recommended for You:
For your use case (Fossetts Circus):
– Create class with `reviewStatus: 'UNDER_REVIEW'`
– Generate JWT and distribute links normally
– Your customers can add the pass to their Google Wallet **immediately**
—
Would you like a version of the class payload optimized for quick pass delivery (without needing approval)?