Integrate the Google Wallet API to digitize passes on web

1. Before you begin

The Google Wallet API allows you to engage with users through various predefined types of passes: loyalty card, offer, gift card, event ticket, transit ticket, and boarding pass, all of which come with use-case specific fields and features. We recognize however that these might not fit every use case, and that is why we created a generic pass type. As the name suggests, the generic pass type should be used when your use case does not fit into any of the other specialized types. Here are some example use cases for the generic pass type:

  • Parking passes
  • Library membership cards
  • Stored value vouchers
  • Gym membership cards
  • Insurance cards
  • Reservations of various kinds

You can use generic passes for any use case that can be presented to the user in the form of a card with up to three rows of information, with an optional barcode and optional details section, so long as it is within our Acceptable Use Policy.

The Google Wallet API allows you to create:

  • Pass classes. Think of classes as templates with common information that all your passes belonging to a program or event share. All pass objects belong to a class.
  • Pass objects serve a concrete purpose associated with your activity as a business and your users (eg.: entry ticket for user A, loyalty card for user B). Each of these items are associated with a previously defined class, and inherit common properties from it.

This codelab will provide you with a pre-defined class, and guide you through creating a pass object, and adding the "Add to Google Wallet" button to your web app, which will allow your users to save the pass to their Google Wallet.

For more information on the Google Wallet API, or adding an "Add to Google Wallet" button to an Android application, please visit the Google Wallet developer documentation.

Prerequisites

  • Basic knowledge of HTML and JavaScript

What You'll Learn

  • How to create pass objects using the Wallet API
  • How to create a JWT for the pass object, and embed it within a "Add to Google Wallet" button in a web app

What you'll need

2. Get set up

Create your cloud project

Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or Workspace account, you must create one.)

Remember the project ID, a unique name across all Google Cloud projects. It will be referred to next.

Get a service account key

The Google Wallet APIs use service accounts for authentication. In this step, we will create a service account and download its service account key file. We will use the gcloud command-line tool, so make sure to install it, if you haven't before.

Open up your terminal, and define a PROJECT_ID variable. For example if your project ID was my-cloud-project:

export PROJECT_ID=my-cloud-project

Run the following command to create a service account:

gcloud iam service-accounts create wallet-codelab --project=$PROJECT_ID

Run the following command, which will create and download a service account key file:

gcloud iam service-accounts keys create ./key.json --iam-account=wallet-codelab@$PROJECT_ID.iam.gserviceaccount.com --project=$PROJECT_ID

Note the path to the key.json file in the current directory (the following command will display it), as we will reference the path to the service account key file in the next steps:

echo $(pwd)/key.json

Enable the Wallet API

Enable the Wallet API using the following command:

gcloud services enable walletobjects.googleapis.com --project=$PROJECT_ID

Create a temporary issuer account

To create passes for your user, you first need to create an issuer account, enable the Wallet API, and then create a class, all of which can be done via the Google Pay Business Console. However, access to this is only granted once the approval process is complete, so for this codelab we will create both a temporary issuer account, and a pass class for you.

  1. Click Create a temporary issuer account and a sample class.
  1. Take note of the issuer ID and class ID, which you will need in the upcoming steps.

This is how the newly created class looks:

{
    "id": "999999.d1fa-4cca1...",
    "classTemplateInfo": {
        "cardTemplateOverride": {
            "cardRowTemplateInfos": [
                {
                    "twoItems": {
                        "startItem": {
                            "firstValue": {
                                "fields": [
                                    {
                                        "fieldPath": "object.textModulesData['points']"
                                    }
                                ]
                            }
                        },
                        "endItem": {
                            "firstValue": {
                                "fields": [
                                    {
                                        "fieldPath": "object.textModulesData['contacts']"
                                    }
                                ]
                            }
                        }
                    }
                }
            ]
        },
        "detailsTemplateOverride": {
            "detailsItemInfos": [
                {
                    "item": {
                        "firstValue": {
                            "fields": [
                                {
                                    "fieldPath": "class.imageModulesData['event_banner']"
                                }
                            ]
                        }
                    }
                },
                {
                    "item": {
                        "firstValue": {
                            "fields": [
                                {
                                    "fieldPath": "class.textModulesData['game_overview']"
                                }
                            ]
                        }
                    }
                },
                {
                    "item": {
                        "firstValue": {
                            "fields": [
                                {
                                    "fieldPath": "class.linksModuleData.uris['official_site']"
                                }
                            ]
                        }
                    }
                }
            ]
        }
    },
    "imageModulesData": [
        {
            "mainImage": {
                "sourceUri": {
                    "uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/google-io-2021-card.png"
                },
                "contentDescription": {
                    "defaultValue": {
                        "language": "en",
                        "value": "Google I/O 2022 Banner"
                    }
                }
            },
            "id": "event_banner'"
        }
    ],
    "textModulesData": [
        {
            "header": "Gather points meeting new people at Google I/O",
            "body": "Join the game and accumulate points in this badge by meeting other attendees in the event.",
            "id": "game_overview"
        }
    ],
    "linksModuleData": {
        "uris": [
            {
                "uri": "https://io.google/2022/",
                "description": "Official I/O '22 Site",
                "id": "official_site"
            }
        ]
    }
}

Get the source code

Clone the repository with the following command:

git clone -b start git@github.com:google-pay/wallet-web-codelab.git
cd wallet-web-codelab

Install project dependencies

The sample app consists of a simple Node.js app. Install the dependencies with the following command:

npm install .

You should now be able to run the Node.js app with the following command:

node app.js

And see it running in your browser:

3. Create a pass object

Open up the file app.js in your favorite text editor. You'll see the Node.js app contains an empty createPassAndToken function which is called when the email address form is submitted. When the form is submitted, it writes the response from the createPassAndToken function out to the web page. Right now it simply returns the text "Form submitted!". We'll modify the createPassAndToken function to use the email address submitted as the ID to create a unique pass object, and then create a JWT which we will use to build the URL that the user can visit, to save the pass to their Google Wallet.

First, let's define some variables. Edit the following lines at the top of the file, using the values defined earlier - the path where your service account key JSON file was saved to, and the issuer and class IDs given when your temporary issuer account was created:

const serviceAccountFile = '/path/to/key.json';
const issuerId = '';
const classId = '';

Next, replace the createPassAndToken function with the following code, which creates an authenticated HTTP client and calls the Google Wallet API to create a pass object, using the user's email address as the object's ID:

async function createPassAndToken(req, res) {
  const credentials = require(serviceAccountFile);
  const httpClient = new GoogleAuth({
    credentials: credentials,
    scopes: 'https://www.googleapis.com/auth/wallet_object.issuer'
  });

  const objectUrl = 'https://walletobjects.googleapis.com/walletobjects/v1/genericObject/';
  const objectPayload = require('./generic-pass.json');

  objectPayload.id = `${issuerId}.${req.body.email.replace(/[^\w.-]/g, '_')}-${classId}`;
  objectPayload.classId = `${issuerId}.${classId}`;

  let objectResponse;
  try {
    objectResponse = await httpClient.request({url: objectUrl + objectPayload.id, method: 'GET'});
    console.log('existing object', objectPayload.id);
  } catch (err) {
    if (err.response && err.response.status === 404) {
      objectResponse = await httpClient.request({url: objectUrl, method: 'POST', data: objectPayload});
      console.log('new object', objectPayload.id);
    } else {
      console.error(err);
      throw err;
    }
  }

  res.send("Form submitted!");
}

The HTTP client first tries to retrieve the existing object if it has already been created, and if it hasn't, it creates it. Enter your email address into the form a few times, and note the different outputs in your terminal.

Pass JSON

Notice the line const objectPayload = require('./generic-pass.json');. This contains the JSON representation of the pass object. Open the file generic-pass.json and try editing some values to customize the pass, before creating it again with a new email address.

4. Create a JWT for saving to Google Wallet

We're almost done. Lastly, we will create a signed JWT which we will use to create the URL for the "Add to Google Wallet" button, which we then send back to the web page. When the user clicks the button, their pass will be saved to their Google Wallet.

At the end of the createPassAndToken function, replace the last line res.send("Form submitted!"); with the following code, which creates a JWT and sends the button HTML back to the web page:

const claims = {
  iss: credentials.client_email, // `client_email` in service account file.
  aud: 'google',
  origins: ['http://localhost:3000'],
  typ: 'savetowallet',
  payload: {
    genericObjects: [{id: objectPayload.id}],
  },
};

const token = jwt.sign(claims, credentials.private_key, {algorithm: 'RS256'});
const saveUrl = `https://pay.google.com/gp/v/save/${token}`;
res.send(`<a href="${saveUrl}"><img src="button.png"></a>`);

5. Congratulations

Congratulations, you have integrated the Google Wallet API on Android successfully!

Sign up for an issuer account

When you are ready to issue your own passes in production, go to the Google Pay & Wallet Console to request access to the Google Wallet API. Check out the documentation to learn more.