Go samples

This commit is contained in:
Stephen McDonald
2023-06-15 10:11:42 +10:00
parent a9213885dd
commit e6b07e4f8c
13 changed files with 4608 additions and 0 deletions

78
go/README.md Normal file
View File

@@ -0,0 +1,78 @@
# Google Wallet Go samples
## Overview
The files in this directory each implement a demo type for a specific Google
Wallet pass type. Each demo type has methods implemented for performing tasks such as
creating a pass class, updating issuer permissions, and more.
| Pass type | File |
|----------------------------|------------------------------------------------|
| Event tickets | [`demo_eventticket.go`](./demo_eventticket.go) |
| Flight boarding passes | [`demo_flight.go`](./demo_flight.go) |
| Generic passes | [`demo_generic.go`](./demo_generic.go) |
| Gift cards | [`demo_giftcard.go`](./demo_giftcard.go) |
| Loyalty program membership | [`demo_loyalty.go`](./demo_loyalty.go) |
| Offers and promotions | [`demo_offer.go`](./demo_offer.go) |
| Transit passes | [`demo_transit.go`](./demo_transit.go) |
## Prerequisites
* Go 1.20.x
* Follow the steps outlined in the
[Google Wallet prerequisites](https://developers.google.com/wallet/generic/web/prerequisites)
to create the Google Wallet issuer account and Google Cloud service account
## Environment variables
The following environment variables must be set. Alternatively, you can update
the code files to set the values directly.
| Enviroment variable | Description | Example |
|----------------------------------|-------------------------------------------------|---------------------|
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to a Google Cloud service account key file | `/path/to/key.json` |
| `WALLET_ISSUER_ID` | Your Google Wallet Issuer ID | 1234567890 |
## How to use the code samples
1. First install the dependencies for the sample you wish to run (this isn't necessary a second time for running subsequent samples)
```bash
go install demo_eventticket.go
```
2. Run the sample
```bash
go run demo_eventticket.go
```
3. Optionally, you can manually copy the demo type in your own project. An example
can be found below
```go
// Create a demo type instance
// Creates the authenticated HTTP client
d := demoEventticket{}
d.auth()
// Create a pass class
d.createClass(issuerId, classSuffix)
// Create a pass object
d.createObject(issuerId, classSuffix, objectSuffix)
// Expire a pass object
d.expireObject(issuerId, objectSuffix)
// Create an "Add to Google Wallet" link
// that generates a new pass class and object
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
// Create an "Add to Google Wallet" link
// that references existing pass classes and objects
d.createJwtExistingObjects(issuerId)
// Create pass objects in batch
d.batchCreateObjects(issuerId, classSuffix)
```

571
go/demo_eventticket.go Normal file
View File

@@ -0,0 +1,571 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketObject"
)
// [END setup]
type demoEventticket struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoEventticket) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoEventticket) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"eventId": "EVENT_ID",
"eventName": {
"defaultValue": {
"value": "Event name",
"language": "en-US"
}
},
"issuerName": "Issuer name",
"id": "%s.%s",
"reviewStatus": "UNDER_REVIEW"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoEventticket) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"ticketHolderName": "Ticket holder name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"ticketNumber": "Ticket number",
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"seatInfo": {
"gate": {
"defaultValue": {
"value": "A",
"language": "en-US"
}
},
"section": {
"defaultValue": {
"value": "5",
"language": "en-US"
}
},
"row": {
"defaultValue": {
"value": "G3",
"language": "en-US"
}
},
"seat": {
"defaultValue": {
"value": "42",
"language": "en-US"
}
}
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoEventticket) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoEventticket) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"eventId": "EVENT_ID",
"eventName": {
"defaultValue": {
"value": "Event name",
"language": "en-US"
}
},
"issuerName": "Issuer name",
"id": "%s.%s",
"reviewStatus": "UNDER_REVIEW"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"ticketHolderName": "Ticket holder name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"ticketNumber": "Ticket number",
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"seatInfo": {
"gate": {
"defaultValue": {
"value": "A",
"language": "en-US"
}
},
"section": {
"defaultValue": {
"value": "5",
"language": "en-US"
}
},
"row": {
"defaultValue": {
"value": "G3",
"language": "en-US"
}
},
"seat": {
"defaultValue": {
"value": "42",
"language": "en-US"
}
}
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoEventticket) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoEventticket) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"ticketHolderName": "Ticket holder name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"ticketNumber": "Ticket number",
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"seatInfo": {
"gate": {
"defaultValue": {
"value": "A",
"language": "en-US"
}
},
"section": {
"defaultValue": {
"value": "5",
"language": "en-US"
}
},
"row": {
"defaultValue": {
"value": "G3",
"language": "en-US"
}
},
"seat": {
"defaultValue": {
"value": "42",
"language": "en-US"
}
}
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/eventTicketObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_eventticket.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoEventticket{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

531
go/demo_flight.go Normal file
View File

@@ -0,0 +1,531 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/flightClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/flightObject"
)
// [END setup]
type demoFlight struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoFlight) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoFlight) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"origin": {
"terminal": "1",
"gate": "A2",
"airportIataCode": "LAX"
},
"flightHeader": {
"carrier": {
"carrierIataCode": "LX"
},
"flightNumber": "123"
},
"localScheduledDepartureDateTime": "2023-07-02T15:30:00",
"reviewStatus": "UNDER_REVIEW",
"issuerName": "Issuer name",
"destination": {
"terminal": "2",
"gate": "C3",
"airportIataCode": "SFO"
},
"id": "%s.%s"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoFlight) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerName": "Passenger name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"boardingAndSeatingInfo": {
"boardingGroup": "B",
"seatNumber": "42"
},
"reservationInfo": {
"confirmationCode": "Confirmation code"
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoFlight) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoFlight) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"origin": {
"terminal": "1",
"gate": "A2",
"airportIataCode": "LAX"
},
"flightHeader": {
"carrier": {
"carrierIataCode": "LX"
},
"flightNumber": "123"
},
"localScheduledDepartureDateTime": "2023-07-02T15:30:00",
"reviewStatus": "UNDER_REVIEW",
"issuerName": "Issuer name",
"destination": {
"terminal": "2",
"gate": "C3",
"airportIataCode": "SFO"
},
"id": "%s.%s"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerName": "Passenger name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"boardingAndSeatingInfo": {
"boardingGroup": "B",
"seatNumber": "42"
},
"reservationInfo": {
"confirmationCode": "Confirmation code"
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoFlight) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoFlight) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerName": "Passenger name",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"boardingAndSeatingInfo": {
"boardingGroup": "B",
"seatNumber": "42"
},
"reservationInfo": {
"confirmationCode": "Confirmation code"
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/flightObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_flight.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoFlight{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

523
go/demo_generic.go Normal file
View File

@@ -0,0 +1,523 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/genericClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/genericObject"
)
// [END setup]
type demoGeneric struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoGeneric) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoGeneric) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"id": "%s.%s"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoGeneric) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"cardTitle": {
"defaultValue": {
"value": "Generic card title",
"language": "en-US"
}
},
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"header": {
"defaultValue": {
"value": "Generic header",
"language": "en-US"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"logo": {
"contentDescription": {
"defaultValue": {
"value": "Generic card logo",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/pass_google_logo.jpg"
}
},
"hexBackgroundColor": "#4285f4",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoGeneric) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoGeneric) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"id": "%s.%s"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"cardTitle": {
"defaultValue": {
"value": "Generic card title",
"language": "en-US"
}
},
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"header": {
"defaultValue": {
"value": "Generic header",
"language": "en-US"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"logo": {
"contentDescription": {
"defaultValue": {
"value": "Generic card logo",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/pass_google_logo.jpg"
}
},
"hexBackgroundColor": "#4285f4",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoGeneric) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoGeneric) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"cardTitle": {
"defaultValue": {
"value": "Generic card title",
"language": "en-US"
}
},
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"header": {
"defaultValue": {
"value": "Generic header",
"language": "en-US"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"logo": {
"contentDescription": {
"defaultValue": {
"value": "Generic card logo",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/pass_google_logo.jpg"
}
},
"hexBackgroundColor": "#4285f4",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/genericObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_generic.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoGeneric{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

500
go/demo_giftcard.go Normal file
View File

@@ -0,0 +1,500 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/giftCardClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/giftCardObject"
)
// [END setup]
type demoGiftcard struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoGiftcard) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoGiftcard) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"issuerName": "Issuer name",
"id": "%s.%s",
"reviewStatus": "UNDER_REVIEW"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoGiftcard) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"pin": "1234",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"balanceUpdateTime": {
"date": "2020-04-12T16:20:50.52-04:00"
},
"state": "ACTIVE",
"cardNumber": "Card number",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"balance": {
"micros": 20000000,
"currencyCode": "USD"
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoGiftcard) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoGiftcard) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"issuerName": "Issuer name",
"id": "%s.%s",
"reviewStatus": "UNDER_REVIEW"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"pin": "1234",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"balanceUpdateTime": {
"date": "2020-04-12T16:20:50.52-04:00"
},
"state": "ACTIVE",
"cardNumber": "Card number",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"balance": {
"micros": 20000000,
"currencyCode": "USD"
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoGiftcard) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoGiftcard) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"pin": "1234",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"balanceUpdateTime": {
"date": "2020-04-12T16:20:50.52-04:00"
},
"state": "ACTIVE",
"cardNumber": "Card number",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"balance": {
"micros": 20000000,
"currencyCode": "USD"
},
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/giftCardObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_giftcard.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoGiftcard{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

521
go/demo_loyalty.go Normal file
View File

@@ -0,0 +1,521 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/loyaltyClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject"
)
// [END setup]
type demoLoyalty struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoLoyalty) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoLoyalty) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"programName": "Program name",
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"id": "%s.%s",
"programLogo": {
"contentDescription": {
"defaultValue": {
"value": "Logo description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm8.staticflickr.com/7340/11177041185_a61a7f2139_o.jpg"
}
}
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoLoyalty) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"accountName": "Account name",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s",
"loyaltyPoints": {
"balance": {
"int": 800
},
"label": "Points"
},
"accountId": "Account id"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoLoyalty) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoLoyalty) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"programName": "Program name",
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"id": "%s.%s",
"programLogo": {
"contentDescription": {
"defaultValue": {
"value": "Logo description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm8.staticflickr.com/7340/11177041185_a61a7f2139_o.jpg"
}
}
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"accountName": "Account name",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s",
"loyaltyPoints": {
"balance": {
"int": 800
},
"label": "Points"
},
"accountId": "Account id"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoLoyalty) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoLoyalty) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"accountName": "Account name",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s",
"loyaltyPoints": {
"balance": {
"int": 800
},
"label": "Points"
},
"accountId": "Account id"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/loyaltyObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_loyalty.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoLoyalty{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

503
go/demo_offer.go Normal file
View File

@@ -0,0 +1,503 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/offerClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/offerObject"
)
// [END setup]
type demoOffer struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoOffer) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoOffer) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"redemptionChannel": "ONLINE",
"reviewStatus": "UNDER_REVIEW",
"title": "Offer title",
"issuerName": "Issuer name",
"provider": "Provider name",
"id": "%s.%s"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoOffer) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"validTimeInterval": {
"start": {
"date": "2023-06-12T23:20:50.52Z"
},
"end": {
"date": "2023-12-12T23:20:50.52Z"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoOffer) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoOffer) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"redemptionChannel": "ONLINE",
"reviewStatus": "UNDER_REVIEW",
"title": "Offer title",
"issuerName": "Issuer name",
"provider": "Provider name",
"id": "%s.%s"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"validTimeInterval": {
"start": {
"date": "2023-06-12T23:20:50.52Z"
},
"end": {
"date": "2023-12-12T23:20:50.52Z"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoOffer) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoOffer) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"validTimeInterval": {
"start": {
"date": "2023-06-12T23:20:50.52Z"
},
"end": {
"date": "2023-12-12T23:20:50.52Z"
}
},
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/offerObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_offer.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoOffer{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

578
go/demo_transit.go Normal file
View File

@@ -0,0 +1,578 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/transitClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/transitObject"
)
// [END setup]
type demoTransit struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demoTransit) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demoTransit) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`
{
"logo": {
"contentDescription": {
"defaultValue": {
"value": "Logo description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://live.staticflickr.com/65535/48690277162_cd05f03f4d_o.png"
}
},
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"id": "%s.%s",
"transitType": "BUS"
}
`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demoTransit) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerNames": "Passenger names",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"ticketLeg": {
"destinationStationCode": "SFO",
"destinationName": {
"defaultValue": {
"value": "Destination name",
"language": "en-US"
}
},
"arrivalDateTime": "2020-04-12T20:20:50.52Z",
"originStationCode": "LA",
"originName": {
"defaultValue": {
"value": "Origin name",
"language": "en-US"
}
},
"departureDateTime": "2020-04-12T16:20:50.52Z",
"fareName": {
"defaultValue": {
"value": "Fare name",
"language": "en-US"
}
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"passengerType": "SINGLE_PASSENGER",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"tripType": "ONE_WAY",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demoTransit) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demoTransit) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`
{
"logo": {
"contentDescription": {
"defaultValue": {
"value": "Logo description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://live.staticflickr.com/65535/48690277162_cd05f03f4d_o.png"
}
},
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"id": "%s.%s",
"transitType": "BUS"
}
`, issuerId, classSuffix)
newObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerNames": "Passenger names",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"ticketLeg": {
"destinationStationCode": "SFO",
"destinationName": {
"defaultValue": {
"value": "Destination name",
"language": "en-US"
}
},
"arrivalDateTime": "2020-04-12T20:20:50.52Z",
"originStationCode": "LA",
"originName": {
"defaultValue": {
"value": "Origin name",
"language": "en-US"
}
},
"departureDateTime": "2020-04-12T16:20:50.52Z",
"fareName": {
"defaultValue": {
"value": "Fare name",
"language": "en-US"
}
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"passengerType": "SINGLE_PASSENGER",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"tripType": "ONE_WAY",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demoTransit) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demoTransit) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`
{
"classId": "%s.%s",
"passengerNames": "Passenger names",
"heroImage": {
"contentDescription": {
"defaultValue": {
"value": "Hero image description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
}
},
"ticketLeg": {
"destinationStationCode": "SFO",
"destinationName": {
"defaultValue": {
"value": "Destination name",
"language": "en-US"
}
},
"arrivalDateTime": "2020-04-12T20:20:50.52Z",
"originStationCode": "LA",
"originName": {
"defaultValue": {
"value": "Origin name",
"language": "en-US"
}
},
"departureDateTime": "2020-04-12T16:20:50.52Z",
"fareName": {
"defaultValue": {
"value": "Fare name",
"language": "en-US"
}
}
},
"barcode": {
"type": "QR_CODE",
"value": "QR code"
},
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"passengerType": "SINGLE_PASSENGER",
"state": "ACTIVE",
"linksModuleData": {
"uris": [
{
"id": "LINK_MODULE_URI_ID",
"uri": "http://maps.google.com/",
"description": "Link module URI description"
},
{
"id": "LINK_MODULE_TEL_ID",
"uri": "tel:6505555555",
"description": "Link module tel description"
}
]
},
"imageModulesData": [
{
"id": "IMAGE_MODULE_ID",
"mainImage": {
"contentDescription": {
"defaultValue": {
"value": "Image module description",
"language": "en-US"
}
},
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
}
}
}
],
"textModulesData": [
{
"body": "Text module body",
"header": "Text module header",
"id": "TEXT_MODULE_ID"
}
],
"tripType": "ONE_WAY",
"id": "%s.%s"
}
`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/transitObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_transit.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demoTransit{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}

25
go/go.mod Normal file
View File

@@ -0,0 +1,25 @@
module example.com/wallet
go 1.20
require golang.org/x/oauth2 v0.8.0
require (
cloud.google.com/go/compute/metadata v0.2.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)

37
go/go.sum Normal file
View File

@@ -0,0 +1,37 @@
cloud.google.com/go/compute/metadata v0.2.0 h1:nBbNSZyDpkNlo3DepaaLKVuO7ClyifSAmNloSCZrHnQ=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

6
templates/README.md Normal file
View File

@@ -0,0 +1,6 @@
# Code Generation Code Only
This directory contains files for generating the demos
found in the parent directory. It does not contain
functioning samples for Google Wallet, so do not
use it as such.

466
templates/generate.py Normal file
View File

@@ -0,0 +1,466 @@
#
# Copyright 2022 Google Inc. All rights reserved.
#
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
import os, json
default_class_payload = {
"id": "$class_id",
}
default_object_payload = {
"id": "$object_id",
"classId": "$class_id",
"state": "ACTIVE",
"heroImage": {
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
},
"contentDescription": {
"defaultValue": {
"language": "en-US",
"value": "Hero image description"
}
}
},
"textModulesData": [
{
"header": "Text module header",
"body": "Text module body",
"id": "TEXT_MODULE_ID"
}
],
"linksModuleData": {
"uris": [
{
"uri": "http://maps.google.com/",
"description": "Link module URI description",
"id": "LINK_MODULE_URI_ID"
},
{
"uri": "tel:6505555555",
"description": "Link module tel description",
"id": "LINK_MODULE_TEL_ID"
}
]
},
"imageModulesData": [
{
"mainImage": {
"sourceUri": {
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg"
},
"contentDescription": {
"defaultValue": {
"language": "en-US",
"value": "Image module description"
}
}
},
"id": "IMAGE_MODULE_ID"
}
],
"barcode": {
"type": "QR_CODE",
"value": "QR code"
}
}
payloads = {}
for object_type in ["generic", "offer", "loyalty", "giftCard", "eventTicket", "flight", "transit"]:
payloads[object_type] = {
"$class_payload": dict(default_class_payload),
"$object_payload": dict(default_object_payload),
}
#################
# Generic
#################
payloads["generic"]["$object_payload"].update({
"cardTitle": {
"defaultValue": {
"language": "en-US",
"value": "Generic card title"
}
},
"header": {
"defaultValue": {
"language": "en-US",
"value": "Generic header"
}
},
"hexBackgroundColor": "#4285f4",
"logo": {
"sourceUri": {
"uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/pass_google_logo.jpg"
},
"contentDescription": {
"defaultValue": {
"language": "en-US",
"value": "Generic card logo"
}
}
}
})
#################
# Offer
#################
payloads["offer"]["$class_payload"].update({
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"provider": "Provider name",
"title": "Offer title",
"redemptionChannel": "ONLINE"
})
payloads["offer"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"validTimeInterval": {
"start": {
"date": "2023-06-12T23:20:50.52Z"
},
"end": {
"date": "2023-12-12T23:20:50.52Z"
}
}
})
#################
# Loyalty
#################
payloads["loyalty"]["$class_payload"].update({
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"programName": "Program name",
"programLogo": {
"sourceUri": {
"uri": "http://farm8.staticflickr.com/7340/11177041185_a61a7f2139_o.jpg"
},
"contentDescription": {
"defaultValue": {
"language": "en-US",
"value": "Logo description"
}
}
}
})
payloads["loyalty"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"accountId": "Account id",
"accountName": "Account name",
"loyaltyPoints": {
"label": "Points",
"balance": {
"int": 800
}
}
})
#################
# GiftCard
#################
payloads["giftCard"]["$class_payload"].update({
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
})
payloads["giftCard"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"cardNumber": "Card number",
"pin": "1234",
"balance": {
"micros": 20000000,
"currencyCode": "USD"
},
"balanceUpdateTime": {
"date": "2020-04-12T16:20:50.52-04:00"
}
})
#################
# Eventticket
#################
payloads["eventTicket"]["$class_payload"].update({
"eventId": "EVENT_ID",
"eventName": {
"defaultValue": {
"language": "en-US",
"value": "Event name"
}
},
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW"
})
payloads["eventTicket"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"seatInfo": {
"seat": {
"defaultValue": {
"language": "en-US",
"value": "42"
}
},
"row": {
"defaultValue": {
"language": "en-US",
"value": "G3"
}
},
"section": {
"defaultValue": {
"language": "en-US",
"value": "5"
}
},
"gate": {
"defaultValue": {
"language": "en-US",
"value": "A"
}
}
},
"ticketHolderName": "Ticket holder name",
"ticketNumber": "Ticket number"
})
#################
# Flight
#################
payloads["flight"]["$class_payload"].update({
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"localScheduledDepartureDateTime": "2023-07-02T15:30:00",
"flightHeader": {
"carrier": {
"carrierIataCode": "LX"
},
"flightNumber": "123"
},
"origin": {
"airportIataCode": "LAX",
"terminal": "1",
"gate": "A2"
},
"destination": {
"airportIataCode": "SFO",
"terminal": "2",
"gate": "C3"
}
})
payloads["flight"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"passengerName": "Passenger name",
"boardingAndSeatingInfo": {
"boardingGroup": "B",
"seatNumber": "42"
},
"reservationInfo": {
"confirmationCode": "Confirmation code"
}
})
#################
# Transit
#################
payloads["transit"]["$class_payload"].update({
"issuerName": "Issuer name",
"reviewStatus": "UNDER_REVIEW",
"logo": {
"sourceUri": {
"uri": "https://live.staticflickr.com/65535/48690277162_cd05f03f4d_o.png"
},
"contentDescription": {
"defaultValue": {
"language": "en-US",
"value": "Logo description"
}
}
},
"transitType": "BUS"
})
payloads["transit"]["$object_payload"].update({
"locations": [
{
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
],
"passengerType": "SINGLE_PASSENGER",
"passengerNames": "Passenger names",
"tripType": "ONE_WAY",
"ticketLeg": {
"originStationCode": "LA",
"originName": {
"defaultValue": {
"language": "en-US",
"value": "Origin name"
}
},
"destinationStationCode": "SFO",
"destinationName": {
"defaultValue": {
"language": "en-US",
"value": "Destination name"
}
},
"departureDateTime": "2020-04-12T16:20:50.52Z",
"arrivalDateTime": "2020-04-12T20:20:50.52Z",
"fareName": {
"defaultValue": {
"language": "en-US",
"value": "Fare name"
}
}
}
})
def indent(s, spaces):
return s.replace("\n", "\n" + (" " * spaces))
def format_payload_dotnet(payload, _):
output = []
payload = (payload
.replace(' "', " ")
.replace('": ', " = ")
.replace(" string = ", ' @string = ')
.replace("]", "}"))
for line in payload.split("\n"):
_indent = len(line) - len(line.lstrip(" "))
if line.endswith("{"):
line = line[:-1] + "new\n%s{" % (" " * _indent)
if line.endswith("["):
line = line[:-1] + "new object[]\n%s{" % (" " * _indent)
output.append(line)
return "\n".join(output)
def format_payload_go(payload, name):
payload = ("\n" + payload.replace(" ", "\t") + "\n").replace("\n", "\n\t").replace(", \n", ",\n")
if name == "$batch_object_payload":
payload = payload.replace("\n", "\n\t")
return payload
lang_config = {
"go": {
"ext": "go",
"class_id": '"%s.%s"',
"object_id": '"%s.%s"',
"filename": lambda s: "demo_%s.go" % s.lower(),
"formatter": format_payload_go,
},
"java": {
"ext": "java",
"class_id": "%s.%s",
"object_id": "%s",
"formatter": lambda s, _: '\n "' + s.replace('"', '\\"').replace("\n", '"\n + "') + '"',
"filename": lambda s: "src/main/java/Demo%s.java" % s.title(),
},
"python": {
"ext": "py",
"class_id": '"%s.%s" % (issuer_id, class_id)',
"object_id": "object_id",
"filename": lambda s: "demo_%s.py" % s.lower(),
},
"nodejs": {
"ext": "js",
"class_id": "`${issuerId}.${classId}`",
"object_id": "objectId",
"formatter": lambda s, _: indent(s, 2),
"filename": lambda s: "demo-%s.js" % s.lower(),
},
"php": {
"ext": "php",
"class_id": '"{$issuerId}.{$classId}"',
"object_id": '"{$objectId}"',
"filename": lambda s: "demo_%s.php" % s.lower(),
},
"dotnet": {
"ext": "cs",
"class_id": '$"{issuerId}.{classId}"',
"object_id": "objectId",
"formatter": format_payload_dotnet,
"filename": lambda s: "Demo%s.cs.example" % s.title(),
"indent": 4,
},
}
path = lambda *s: os.path.join(os.path.dirname(os.path.abspath(__file__)), *s)
for lang, config in lang_config.items():
try:
with open(path("template.%s" % config["ext"]), "r") as f:
template = f.read()
except IOError:
continue
for object_type, content in payloads.items():
output = template
# JSON payloads
if "$batch_object_payload" not in content:
content["$batch_object_payload"] = content["$object_payload"]
for name, value in content.items():
payload = json.dumps(value, indent=config.get("indent", 2))
if "formatter" in config:
payload = config["formatter"](payload, name)
output = output.replace(name, payload)
# code placeholders
config["object_type"] = object_type
config["object_type_lowercase"] = object_type.lower()
config["object_type_titlecase"] = object_type.title()
for name in ("object_type_titlecase", "object_type_lowercase", "object_type", "class_id", "object_id"):
output = output.replace('"$%s"' % name, config[name])
output = output.replace('$%s' % name, config[name])
with open(path("..", lang, config["filename"](object_type)), "w") as f:
f.write(output)

269
templates/template.go Normal file
View File

@@ -0,0 +1,269 @@
/*
* Copyright 2023 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [START setup]
// [START imports]
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
oauthJwt "golang.org/x/oauth2/jwt"
"io"
"net/http"
"os"
"strings"
)
// [END imports]
const (
batchUrl = "https://walletobjects.googleapis.com/batch"
classUrl = "https://walletobjects.googleapis.com/walletobjects/v1/$object_typeClass"
objectUrl = "https://walletobjects.googleapis.com/walletobjects/v1/$object_typeObject"
)
// [END setup]
type demo$object_type_titlecase struct {
credentials *oauthJwt.Config
httpClient *http.Client
batchUrl, classUrl, objectUrl string
}
// [START auth]
// Create authenticated HTTP client using a service account file.
func (d *demo$object_type_titlecase) auth() {
b, _ := os.ReadFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
credentials, _ := google.JWTConfigFromJSON(b, "https://www.googleapis.com/auth/wallet_object.issuer")
d.credentials = credentials
d.httpClient = d.credentials.Client(oauth2.NoContext)
}
// [END auth]
// [START createClass]
// Create a class.
func (d *demo$object_type_titlecase) createClass(issuerId, classSuffix string) {
newClass := fmt.Sprintf(`$class_payload`, issuerId, classSuffix)
res, err := d.httpClient.Post(classUrl, "application/json", bytes.NewBuffer([]byte(newClass)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Class insert response:\n%s\n", b)
}
}
// [END createClass]
// [START createObject]
// Create an object.
func (d *demo$object_type_titlecase) createObject(issuerId, classSuffix, objectSuffix string) {
newObject := fmt.Sprintf(`$object_payload`, issuerId, classSuffix, issuerId, objectSuffix)
res, err := d.httpClient.Post(objectUrl, "application/json", bytes.NewBuffer([]byte(newObject)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object insert response:\n%s\n", b)
}
}
// [END createObject]
// [START expireObject]
// Expire an object.
//
// Sets the object's state to Expired. If the valid time interval is
// already set, the pass will expire automatically up to 24 hours after.
func (d *demo$object_type_titlecase) expireObject(issuerId, objectSuffix string) {
patchBody := `{"state": "EXPIRED"}`
url := fmt.Sprintf("%s/%s.%s", objectUrl, issuerId, objectSuffix)
req, _ := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer([]byte(patchBody)))
res, err := d.httpClient.Do(req)
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Object expiration response:\n%s\n", b)
}
}
// [END expireObject]
// [START jwtNew]
// Generate a signed JWT that creates a new pass class and object.
//
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass class and object defined in the JWT are
// created. This allows you to create multiple pass classes and objects in
// one API call when the user saves the pass to their wallet.
func (d *demo$object_type_titlecase) createJwtNewObjects(issuerId, classSuffix, objectSuffix string) {
newClass := fmt.Sprintf(`$class_payload`, issuerId, classSuffix)
newObject := fmt.Sprintf(`$object_payload`, issuerId, classSuffix, issuerId, objectSuffix)
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"genericClasses": [%s],
"genericObjects": [%s]
}
`, newClass, newObject)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtNew]
// [START jwtExisting]
// Generate a signed JWT that references an existing pass object.
// When the user opens the "Add to Google Wallet" URL and saves the pass to
// their wallet, the pass objects defined in the JWT are added to the
// user's Google Wallet app. This allows the user to save multiple pass
// objects in one API call.
func (d *demo$object_type_titlecase) createJwtExistingObjects(issuerId string) {
var payload map[string]interface{}
json.Unmarshal([]byte(fmt.Sprintf(`
{
"eventTicketObjects": [{
"id": "%s.EVENT_OBJECT_SUFFIX",
"classId": "%s.EVENT_CLASS_SUFFIX"
}],
"flightObjects": [{
"id": "%s.FLIGHT_OBJECT_SUFFIX",
"classId": "%s.FLIGHT_CLASS_SUFFIX"
}],
"genericObjects": [{
"id": "%s.GENERIC_OBJECT_SUFFIX",
"classId": "%s.GENERIC_CLASS_SUFFIX"
}],
"giftCardObjects": [{
"id": "%s.GIFT_CARD_OBJECT_SUFFIX",
"classId": "%s.GIFT_CARD_CLASS_SUFFIX"
}],
"loyaltyObjects": [{
"id": "%s.LOYALTY_OBJECT_SUFFIX",
"classId": "%s.LOYALTY_CLASS_SUFFIX"
}],
"offerObjects": [{
"id": "%s.OFFER_OBJECT_SUFFIX",
"classId": "%s.OFFER_CLASS_SUFFIX"
}],
"transitObjects": [{
"id": "%s.TRANSIT_OBJECT_SUFFIX",
"classId": "%s.TRANSIT_CLASS_SUFFIX"
}]
}
`, issuerId)), &payload)
claims := jwt.MapClaims{
"iss": d.credentials.Email,
"aud": "google",
"origins": []string{"www.example.com"},
"typ": "savetowallet",
"payload": payload,
}
// The service account credentials are used to sign the JWT
key, _ := jwt.ParseRSAPrivateKeyFromPEM(d.credentials.PrivateKey)
token, _ := jwt.NewWithClaims(jwt.SigningMethodRS256, claims).SignedString(key)
fmt.Println("Add to Google Wallet link")
fmt.Println("https://pay.google.com/gp/v/save/" + token)
}
// [END jwtExisting]
// [START batch]
// Batch create Google Wallet objects from an existing class.
func (d *demo$object_type_titlecase) batchCreateObjects(issuerId, classSuffix string) {
data := ""
for i := 0; i < 3; i++ {
objectSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
batchObject := fmt.Sprintf(`$batch_object_payload`, issuerId, classSuffix, issuerId, objectSuffix)
data += "--batch_createobjectbatch\n"
data += "Content-Type: application/json\n\n"
data += "POST /walletobjects/v1/$object_typeObject\n\n"
data += batchObject + "\n\n"
}
data += "--batch_createobjectbatch--"
res, err := d.httpClient.Post(batchUrl, "multipart/mixed; boundary=batch_createobjectbatch", bytes.NewBuffer([]byte(data)))
if err != nil {
fmt.Println(err)
} else {
b, _ := io.ReadAll(res.Body)
fmt.Printf("Batch insert response:\n%s\n", b)
}
}
// [END batch]
func main() {
if len(os.Args) == 0 {
fmt.Println("Usage: go run demo_$object_type_lowercase.go <issuer-id>")
os.Exit(1)
}
issuerId := os.Getenv("WALLET_ISSUER_ID")
classSuffix := strings.ReplaceAll(uuid.New().String(), "-", "_")
objectSuffix := fmt.Sprintf("%s-%s", strings.ReplaceAll(uuid.New().String(), "-", "_"), classSuffix)
d := demo$object_type_titlecase{}
d.auth()
d.createClass(issuerId, classSuffix)
d.createObject(issuerId, classSuffix, objectSuffix)
d.expireObject(issuerId, objectSuffix)
d.createJwtNewObjects(issuerId, classSuffix, objectSuffix)
d.createJwtExistingObjects(issuerId)
d.batchCreateObjects(issuerId, classSuffix)
}