-
Notifications
You must be signed in to change notification settings - Fork 0
Design Document: Google Wallet Ticket Generation
Reference: https://developers.google.com/wallet/generic/web
SocialPass allows users to generate tickets if they are able to prove ownership of certain required digital assets (such as NFTs) set by the event organizers. One useful feature after proving ownership and generating the ticket is saving the ticket to the user’s Google Wallet. This page is meant to document the required interactions with the Google API in order to allow users to accomplish the above stated goal.
The overview of the flow is fairly simple and consists of the following steps:
- Authenticate (with the required credentials)
- Generate a pass object
- Save the pass object (in the user’s Google wallet)
The above steps are documented in detail in the following sections:
The first step is to install the required Google client library in Python using the following command:
pip install --upgrade google-api-python-client
Once that’s done, the following code can be run to authenticate the client:
import os, re, datetime
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
from google.auth import jwt, crypt
# Path to service account key file obtained from Google CLoud Console.
service_account_file = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "/path/to/key.json")
# Issuer ID obtained from Google Pay Business Console.
issuer_id = os.environ.get("WALLET_ISSUER_ID", "<issuer ID>")
# Developer defined ID for the wallet class.
class_id = os.environ.get("WALLET_CLASS_ID", "test-generic-class-id")
# Developer defined ID for the user, eg an email address.
user_id = os.environ.get("WALLET_USER_ID", "[email protected]")
# ID for the wallet object, must be in the form `issuer_id.user_id` where user_id is alphanumeric.
object_id = "%s.%s-%s" % (issuer_id, re.sub(r"[^\w.-]", "_", user_id), class_id)
# Authenticate the client
credentials = service_account.Credentials.from_service_account_file(service_account_file,
scopes=["https://www.googleapis.com/auth/wallet_object.issuer"])
http_client = AuthorizedSession(credentials)
In order to get the <issuer ID>
, the following steps must be taken:
- Sign up at the Google Pay and Wallet Console (https://pay.google.com/business/console)
- Finish the business profile
- Go to the Google Wallet API page and click Request access
The service_account_file
is a JSON file storing credentials required to access the API. This file can be generated for this use case using the following steps:
- Sign in to the Google Cloud (https://console.cloud.google.com/) and create a GCP project
- Enable the Google Wallet API for this GCP project (https://console.cloud.google.com/apis/library/walletobjects.googleapis.com)
- Create a service account for this GCP project
- Generate a JSON key for this service account (this file is referenced in the code)
- Go the Users page in the Google Pay and Wallet Console
- Invite this service account’s email address as a user with Developer access
Once this is done, you should be able to authenticate successfully and move on to the next step.
Before a pass can be generated, a class needs to be created to hold the pass. This can be done following the steps in this link: https://developers.google.com/wallet/tickets/events/web/prerequisites#5.-create-a-class
Please take note of the following things when creating the class:
- The class must not be in the DRAFT status because the API requests will not work in this state. It can be set to UNDER_REVIEW instead.
- The logo image URL is a common source of error, often without any meaningful error messages. The following guidelines should be kept in mind to avoid this: https://developers.google.com/wallet/generic/resources/brand-guidelines#logos
Important note For our use case in production, we may need to create a class every time an event is created/updated. This means hitting the Google API from a Django signal post save from the event model. Further discussions need to be had on this matter before anything is finalized.
Once this is done, get the class_id
(from the created class) to generate a pass using the following code:
object_url = "https://walletobjects.googleapis.com/walletobjects/v1/eventTicketObject/"
object_payload = {
"id": object_id,
"classId": "%s.%s" % (issuer_id, class_id),
"heroImage": {
"sourceUri": {
"uri": "https://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg",
"description": "Test heroImage description"
}
},
"textModulesData": [
{
"header": "Test text module header",
"body": "Test text module body"
}
],
"linksModuleData": {
"uris": [
{
"kind": "walletobjects#uri",
"uri": "http://maps.google.com/",
"description": "Test link module uri description"
},
{
"kind": "walletobjects#uri",
"uri": "tel:6505555555",
"description": "Test link module tel description"
}
]
},
"imageModulesData": [
{
"mainImage": {
"kind": "walletobjects#image",
"sourceUri": {
"kind": "walletobjects#uri",
"uri": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
"description": "Test image module description"
}
}
}
],
"barcode": {
"kind": "walletobjects#barcode",
"type": "qrCode",
"value": "Test QR Code"
},
"state": "active",
"seatInfo": {
"kind": "walletobjects#eventSeat",
"seat": {
"kind": "walletobjects#localizedString",
"defaultValue": {
"kind": "walletobjects#translatedString",
"language": "en-us",
"value": "42"
}
},
"row": {
"kind": "walletobjects#localizedString",
"defaultValue": {
"kind": "walletobjects#translatedString",
"language": "en-us",
"value": "G3"
}
},
"section": {
"kind": "walletobjects#localizedString",
"defaultValue": {
"kind": "walletobjects#translatedString",
"language": "en-us",
"value": "5"
}
},
"gate": {
"kind": "walletobjects#localizedString",
"defaultValue": {
"kind": "walletobjects#translatedString",
"language": "en-us",
"value": "A"
}
}
},
"ticketHolderName": "Test ticket holder name",
"ticketNumber": "Test ticket number",
"locations": [
{
"kind": "walletobjects#latLongPoint",
"latitude": 37.424015499999996,
"longitude": -122.09259560000001
}
]
}
# Retrieve the object, or create it if it doesn't exist.
object_response = http_client.get(object_url + object_id)
if object_response.status_code == 404:
object_response = http_client.post(object_url, json=object_payload)
print("object GET or POST response:", object_response.text)
If everything works properly, you should see a JSON response. This is the JSON representation of the pass that was just generated. Save the id
from this response and move to the next step.
In order to allow users to save the passes we generate, we need to take the following steps:
- Get a signed JWT (token) for the pass object
- Prompt the user to go the appropriate link using the generated token:
https://pay.google.com/gp/v/save/{token}
When a user goes to this link, they will be automatically asked to save the pass in to their Google Wallet. This link can be shared via email, or a button inside our product on the success page.
The signed JWT (token) can be generated by running the following code:
claims = {
"iss": http_client.credentials.service_account_email, # `client_email` in service account file.
"aud": "google",
"origins": ["socialpass.io"],
"typ": "savetowallet",
"payload": {
"eventTicketObjects": [
{
"id": object_id # the id of the generated pass
}
]
}
}
signer = crypt.RSASigner.from_service_account_file(service_account_file)
token = jwt.encode(signer, claims).decode("utf-8")
save_url = "https://pay.google.com/gp/v/save/%s" % token
print(save_url)