mirror of
https://github.com/google-wallet/rest-samples.git
synced 2026-01-08 05:03:54 -05:00
Go samples
This commit is contained in:
78
go/README.md
Normal file
78
go/README.md
Normal 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
571
go/demo_eventticket.go
Normal 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
531
go/demo_flight.go
Normal 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
523
go/demo_generic.go
Normal 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
500
go/demo_giftcard.go
Normal 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
521
go/demo_loyalty.go
Normal 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
503
go/demo_offer.go
Normal 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
578
go/demo_transit.go
Normal 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
25
go/go.mod
Normal 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
37
go/go.sum
Normal 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
6
templates/README.md
Normal 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
466
templates/generate.py
Normal 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
269
templates/template.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user