Enable seamless loyalty sign-up with Google Pay

Google Pay Passes

Google Pay for Passes makes it easy for your customers to save boarding passes, loyalty programs, offers, gift cards, and tickets to their phones for easy access at the right time. It also lets you engage them through location-based notifications, real-time updates, and more.

The Google Pay Loyalty APIs make it possible for your customers to save your loyalty program to their mobile devices from your website, mobile app, or email. Additionally, your loyalty programs are discoverable from other Google surfaces, including the Google Pay app and Google Maps.

This codelab focuses on how to make and handle Google Pay-related API calls. Non-relevant concepts and code blocks are glossed over and are available for you to dive into later if you would like to understand how it all works.

Prerequisites

  • Basic knowledge of HTML and JavaScript

What you'll do

  • Create a loyalty program
  • Call the Google Pay Passes API to create a loyalty pass
  • Save the loyalty pass to Google Pay
  • Optional: Update the points balance on a loyalty pass
  • Optional: Integrate with the Enrollment API to make it possible to acquire new customers from Google surfaces, such as Google Pay and Google Maps
  • Optional: Add the Save to Google Pay web button to a web page
  • Optional: Add the Save to Google Pay button to an Android app

Sample app screens

What you'll need

Choose a development environment

You can complete the codelab on your local device if you have the following dependencies installed:

  • Node.js
  • git
  • gcloud

Otherwise, the codelab can be completed in Cloud Shell.

Which development environment will you use?

Local environment Cloud Shell

If you would like to use Cloud Shell as a remote development environment for this codelab, click Run in Cloud Shell:

Run in Cloud Shell

Get the source code

Clone the repository with the following command:

# clone sample application
git clone https://github.com/google-pay/gpay-loyaltyapi-demo.git
cd gpay-loyaltyapi-demo

Or, if you prefer a zip archive, click Download source code.

Create a service account and service account key

A service account and service account key are required to authenticate your app with the Passes and Loyalty APIs. The following commands automate the creation and retrieval of a service account key for you. Also included is a script that enables the Google Pay Passes API on your behalf.

  1. Replace my-google-cloud-project-id with your own Google Cloud project ID. The script creates a project for you if it doesn't exist. An example of a project ID that you could use is username-loyalty.
  2. Copy and paste each of the following commands into your console window. You may be asked to authorize access to the Google Cloud SDK, so follow the prompts as required.
# replace my-google-cloud-project-id with your target cloud project id

# sets up MY_PROJECT variable to be used in other commands
MY_PROJECT="my-google-cloud-project-id"

# create service account and service account key
scripts/service-account-key.sh $MY_PROJECT

# tell the application where to find your key
export GCP_CREDENTIALS=`realpath ./key.json`

Enable the Passes API

Enable the Passes API using the Cloud Console, or the command line:

# enable passes api
scripts/enable-api.sh $MY_PROJECT

Install project dependencies

The sample app consists of a React and Firebase app. The scripts/setup.sh script helps to install all project dependencies in a single command.

# install project dependencies
scripts/setup.sh

Test the website

Confirm that the project dependencies are installed correctly by starting and visiting the sample website. This website is used as an example for users to sign up and sign in for your loyalty program, and help you test other features, such as updating point balances.

# build and start firebase hosting and functions
scripts/serve.sh

For local development, click Launch website.

For Cloud Shell development, click Web Preview > Preview on port 8080.

Web preview screenshot

The Merchant Center helps you manage your passes that your customers can save to Google Pay.

In this step, you provide the service account with access to programmatically create and manage passes on your behalf.

Create a temporary issuer account

In order to access the Merchant Center, you need access to an issuer account.

  1. Click Create a temporary issuer account.
  1. Click the link in the confirmation screen to see issuer details.Temporary issuer account created screenshot
  2. Take note of the issuer ID, which you need in the next step.

Tell the app about your issuer account

Provide details to the application about your issuer account:

# replace issuer-id with the issuer id from the previous step
export GOOGLE_PAY_ISSUER_ID="issuer-id"

Grant issuer account permissions

From the Account management page in the Merchant Center, you need to share access to the service account that was created in the previous step so that the service account can start making API calls on your behalf to the Google Pay API for Passes.

  1. Click Share account.

Share account screenshot

  1. Add the service account's email address. It's in the following format (replace my-google-cloud-project-id with your project name): loyalty-web-app@my-google-cloud-project-id.iam.gserviceaccount.com
  2. Select Can Edit from the drop-down list.
  3. Click Add person icon.

Add service account screenshot

  1. Click Save changes

Save changes to issuer permissions screenshot

In this step, you go through the process of defining a loyalty program with the Google Pay Merchant Center.

Define your loyalty program

  1. Click Loyalty Classes on the side menu.
  2. Click Create new to define your loyalty program.

Create loyalty class screenshot

  1. Enter these suggested values:
    • Issuer name: Codelab <your name>
    • Class ID: codelab-demo
    • Status: UNDER_REVIEW
    • Country code: <your country code>
    • Logo image URL: <URL to your image> (if you need an image for the purpose of completing the codelab, you can use https://gpay-loyaltyapi-demo.web.app/images/logo.png)
    • Program name: Codelab Demo
  2. Click Create.

Tell the app about your loyalty program

Execute the following command in your terminal:

# if you've used a different value than codelab-demo in the previous
# step for Class ID field, make sure the value is replaced here
export LOYALTY_PROGRAM="codelab-demo"

In this step, you integrate with the Passes and Loyalty APIs to create a loyalty pass on Google's server. This sets up the pass on the server, but isn't added to the customer's Google Pay account.

Construct a loyaltyObject

A loyalty pass is created by inserting a loyaltyObject using the Google Pay Passes API. This section of the codelab takes you through the steps required to insert a loyaltyObject.

Implement the createLoyaltyObject method

The createLoyaltyObject method is called by createAccount when a user signs up to join your loyalty program. The createLoyaltyObject method is responsible for creating a loyaltyObject based on a user's name, email address, and loyalty points, and posting it to the Google Pay Passes API.

Define the loyaltyObject

Construct the loyaltyObject by using the following as a guide:

functions/src/services/loyalty-service.js
async function createLoyaltyObject(name, email, points) {
  const client = new PassesClient();

  // Read issuerId and loyalty program from config.
  const { issuerId, loyaltyProgram } = config;

  // Step 1: Construct the loyaltyObject.
  const loyaltyObject = {
    id: getLoyaltyId(email),
    classId: `${issuerId}.${loyaltyProgram}`,
    accountId: email,
    accountName: name,
    state: 'active',
    loyaltyPoints: {
      balance: {
        int: points,
      },
      label: 'Points',
    },
  };

  // Step 2: Insert the loyaltyObject.

  return loyaltyObject;
}

Post the loyaltyObject

Post the loyalty object using client.postIfNotFound:

functions/src/services/loyalty-service.js
async function createLoyaltyObject(name, email, points) {
  // ...

  // Step 2: Insert the loyaltyObject.
  await client.postIfNotFound(
    'https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject',
    loyaltyObject,
    loyaltyObject.id,
  );

  // ...
}

Optional: Add a QR code

You can add a barcode or QR code to make the process of using loyalty passes at checkout easier.

Add the barcode element to the loyaltyObject. In this example, the QR code is encoded with the user's email address.

functions/src/services/loyalty-service.js
async function createLoyaltyObject(name, email, points) {
  // ...

  // Step 1: Construct the loyaltyObject.
  const loyaltyObject = {
    // ...
    barcode: {
      type: 'qrCode',
      value: email,
    },
  };

  // ...
}

In this step, you create a JWT, which is used to associate the loyalty pass with the Google Pay user.

A JWT is a specification for sharing claims between two parties. In this case, your app makes claims about the loyalty pass and shares these claims with the Google Pay Passes API. The JWT includes a signature, which is used by the Passes API to verify that these claims have come from your app.

The claims that your app makes about the loyalty pass look like this:

{
  "aud": "google",
  "origins": [
    "https://localhost:8080"
  ],
  "iss": "loyalty-web-app@my-google-cloud-project-id.iam.gserviceaccount.com",
  "typ": "savetowallet",
  "payload": { /* passes information go here */ }
}

Implement the createAccount method

The createAccount method is called when a user signs up to join the loyalty program, which is when a POST request is made to /api/loyalty/create.

In a typical implementation, the app verifies the user's details and sends an email with a verification code before saving the details in the backend. Because this is for demonstration purposes, you skip this step.

Invoke the createLoyaltyObject method

Create the loyaltyObject method by calling createLoyaltyObject:

functions/src/loyalty.js

async function createAccount(req, res) {
  // ...

  // read the name and email from the request body
  const { name, email } = req.body;

  // typical implementation would verify email
  // and save loyalty details to your back-end

  // Step 1: Create a loyaltyObject.
  const loyaltyObject = await createLoyaltyObject(name, email, 0);

  // Step 2: Define the JWT claims.

  // Step 3: Create and sign the JWT.

  // Step 4: Return the token.
  throw new Error('Not implemented');
}

Populate the JWT claims

Create a claims object by populating the fields with the following:

functions/src/loyalty.js

async function createAccount(req, res) {
  // ...

  // Step 1: Create a loyaltyObject.
  const loyaltyObject = await createLoyaltyObject(name, email, 0);

  // Step 2: Define the JWT claims.
  const claims = {
    aud: 'google',
    origins: [website],
    iss: credentials.client_email,
    typ: 'savetowallet',
    payload: {
      loyaltyObjects: [
        {
          id: loyaltyObject.id,
        },
      ],
    },
  };

  // ...
}

Create and sign the JWT

As described above, the JWT includes a signature that is used by the Google Pay Passes API to verify that the claims have come from your app.

Calling the jwt.sign method (using the jsonwebtoken package), passing in the claims defined above and the private_key from the key.json file.

functions/src/loyalty.js

async function createAccount(req, res) {
  // ...

  // Step 3: Create and sign the JWT.
  const token = jwt.sign(claims, credentials.private_key, { algorithm: 'RS256' });

  // ...
}

Return the JWT in the response

The signed JWT should be returned to the client or web page so that it can construct a Save to Google Pay URL:

functions/src/loyalty.js

async function createAccount(req, res) {
  // ...

  // Step 4: Return the token.
  res.json({
    token,
  });
}

The loyalty pass is finally saved to the customer's Google Pay account when they visit the Save to Google Pay URL, which includes the JWT defined in the previous step. The format of the URL looks like this: https://pay.google.com/gp/v/save/${jwt}

In this step, you connect the sign-up form to the API implemented in the previous step and generate an HTML anchor tag that sends the user to the Save to Google Pay URL.

Handle form submission

On form submission, you issue a POST request to /api/loyalty/create to execute the createAccount method that you implemented earlier.

The request includes the name and email obtained from the HTML form.

To do this, implement the submitHandler as follows:

www/src/loyalty/SignUp.js

async function submitHandler(event) {
  event.preventDefault();

  // ...

  // Step 1: Call the API to create a loyaltyObject.
  const result = await fetch('/api/loyalty/create', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name,
      email,
    }),
  });
  const details = await result.json();

  // Step 2: Set JWT based on API response.
  setJwt(details.token);
}

When setJwt is called with the result of the API, the SignUpConfirmation component is rendered (instead of the sign-up form) and the JWT is passed to it.

Here's an example:

www/src/loyalty/SignUp.js

{jwt ? (
  <SignUpConfirmation jwt={jwt} />
) : (
  <section>
    {/* ... */}
  </section>
)}

Using the JWT passed in from the SignUp component, you construct an anchor tag that links to https://pay.google.com/gp/v/save/${jwt}.

www/src/loyalty/SignUpConfirmation.js

function SignUpConfirmation({ jwt }) {
  return (
    <div className="Loyalty">
      <section>
        <h1>Confirmation</h1>
        {/* ... */}

        {/* Step 1: Add save to google pay link. */}
        <a href={`https://pay.google.com/gp/v/save/${jwt}`} target="_blank" rel="noreferrer">
          Save to Google Pay
        </a>
      </section>
    </div>
  );
}

Test the website

# build and start firebase hosting and functions
scripts/serve.sh

For local development: click Launch website.

For Cloud Shell development, click Web Preview > Preview on port 8080.

Web preview screenshot

Confirm that Save to Google Pay works:

  1. Click the Loyalty sign-up link.
  2. Complete and submit the sign-up form.
  3. Click the Save to Google Pay link.
  4. Click Save.

Take note of the URL of the page that launches and leave the page open. You use it in the Enrollment and sign in: Make your loyalty program discoverable section.

In this section, you will learn how to update the points balance on a loyalty pass to simulate how a point-of-sales terminal might integrate with the API to update your customer's point balance.

Update a loyalty pass

Updating loyalty points balance is handled in the updateLoyaltyPoints method in your sample app.

Define the loyaltyObjectUpdate fields

To update the points balance, assign the new points balance to the loyaltyPoints field:

functions/src/services/loyalty-service.js

async function updateLoyaltyPoints(email, points) {
  const client = new PassesClient();
  const loyaltyId = getLoyaltyId(email);

  // Step 1: Define the fields that require updates.
  const loyaltyObject = {
    id: loyaltyId,
    loyaltyPoints: {
      balance: {
        int: points,
      },
    },
  };

  // Step 2: Call the REST API to update points.
}

Call the REST API

To update a loyalty pass, issue a PUT or PATCH request to the target loyaltyObject with the Google Pay Passes API:

functions/src/services/loyalty-service.js

async function updateLoyaltyPoints(email, points) {
  // ...

  // Step 2: Call the REST API to update points.
  await client.patch(
    `https://walletobjects.googleapis.com/walletobjects/v1/loyaltyObject/${loyaltyId}`,
    loyaltyObject,
  );
}

Test the website

Run this command:

# build and start firebase hosting and functions
scripts/serve.sh

For local development, click Launch website.

For Cloud Shell development, click Web Preview > Preview on port 8080.

Web preview screenshot

Confirm that Save to Google Pay works:

  1. Click the Loyalty rewards link.
  2. Enter your email.
  3. Update your points.
  4. Submit the form.
  5. View the updated loyalty pass.

The loyalty enrollment and sign-in feature lets users search for your loyalty program, and join or sign-in to their account from Google Pay.

In this step, you integrate with the enrollment feature so that, when a user finds your loyalty program on Google Pay and chooses to sign up, Google Pay prompts the user to provide consent to shared personal details already saved to their Google account. This makes joining your loyalty program convenient and reduces the number of form fields that they have to manually enter, which increases the chances that they sign up for your loyalty program.

The enrollment and sign-in flow looks like this:

Sample app screens

Make your loyalty program discoverable

To make your loyalty program discoverable, update your loyalty program definition in the Merchant Center:

  1. Scroll to Discoverable Settings.
  2. Click Edit.

Edit enrollment discoverable settings screenshot

  1. Change the state to Set.
  2. Click Save.

Set enrollment discoverable state screenshot

  1. Click Edit.
  2. Apply your updates.
  3. Click Save.

Shared data fields screenshot

Handle enrollment requests

After a user chooses to sign up and provides consent to share their personal information, Google Pay creates a web view posting userProfile information to the account signup URL provided in the previous step.

userProfile is a Base64 JSON representation of the shared data fields. The following is a decoded version of the userProfile field:

{
  "firstName": "Jane",
  "lastName": "Doe",
  "addressLine1": "1600 Amphitheatre Pkwy",
  "addressLine2": "Apt 123",
  "addressLine3": "Attn:Jane",
  "city": "Mountain View",
  "state": "CA",
  "zipcode": "94043",
  "country": "US",
  "email": "jane.doe@example.com",
  "phone": "555-555-5555"
}

/api/loyalty/sign-up

The loyalty sign-up request is handled by the signUp method:

functions/src/loyalty.js

function signUp(req, res) {
  const { userProfile } = req.body;
  const jwt = JSON.parse(Buffer.from(userProfile, 'base64').toString('utf8'));
  const { firstName, lastName, email } = jwt;
  const query = qs.stringify({
    name: [firstName, lastName].filter(n => n).join(' '),
    email,
    mode: 'redirect',
  });

  res.redirect(`/sign-up?${query}`);
}

In the implementation:

  • userProfile is read from the request body.
  • Base64 decoded: Buffer.from(userProfile, 'base64').toString('utf8')
  • Parsed as a JSON object: JSON.parse(/* ... */)
  • Redirect to your existing sign-up page with form values in the query string.

Redirect requests that originate from enrollment

To provide the best mobile user experience and let the Google Pay app save the loyalty pass, read the query string parameter that you set in the previous step and send the user directly to the Save to Google Pay URL:

www/src/loyalty/SignUp.js

async function submitHandler(event) {
  // ...

  // Step 3: Redirect users to Google Pay when in redirect mode.
  if (query.get('mode') === 'redirect') {
    window.location.href = `https://pay.google.com/gp/v/save/${details.token}`;
    return;
  }

  // ...
}

Test the enrollment request

The loyalty pass isn't searchable in the Google Pay app until a test account is created.

To test the integration before the loyalty pass is searchable in Google Pay, start the web server locally and use the Codelab testing tool.

Start the sample website

# build and start firebase hosting and functions
scripts/serve.sh

Launch the codelab testing tool

  1. Click Launch Codelab testing tool. This tool helps you simulate the request issued by Google Pay by taking the fields provided and posting userProfile to the sign-up URL.

Enrollment testing tool screenshot

  1. Fill in the form:
    1. Set the Sign-up URL field to the previously provided account signup URL.
    2. Populate the shared data fields that you requested.
    3. Click Test enrollment to post the userProfile to the sign-up URL.

Request test-account setup

To complete end-to-end testing of your enrollment and sign-up integration, you must first have testing set up on your account. In order to do this, the following prerequisites must be met:

  • Sign up for and receive access for your own issuer account (the temporary account issued for this codelab is ineligible)
  • Deploy your website for Google to independently verify the integration
  • Update the account signup URL and/or account login URL in the Merchant Center to the location of your deployed website

Request access with the registration form.

Web Integration

In this step, you improve upon the existing user experience in three ways:

  1. Display a branded Save to Google Pay button.
  2. Complete the save process within your website.
  3. Reduce the number of clicks to save from two to one.

Example of Save to Google Pay button

Because your sample app is built with React, you integrate with the Save to Google Pay React button.

Install the Save to Google Pay React button

To install the Save to Google Pay React button, run this command:

# install the save button in the React website
(cd www && npm install @google-pay/save-button-react)

Import the Save to Google Pay React button

Import the SaveToGooglePayButton from @google-pay/save-button-react just after the other import statements near the top of the www/src/loyalty/SignUpConfirmation.js file.

www/src/loyalty/SignUpConfirmation.js

import SaveToGooglePayButton from '@google-pay/save-button-react';

Add the button to the confirmation page

Replace the anchor tag (<a></a>) that you added previously with the SaveToGooglePayButton component.

www/src/loyalty/SignUpConfirmation.js

function SignUpConfirmation({ jwt }) {
  return (
    {/* ... */}

    {/* Step 1: Add the Save to Google Pay link. */}
    <SaveToGooglePayButton jwt={jwt} theme="light" height="standard" />
  );
}

Test the website

Run this command:

# build and start firebase hosting and functions
scripts/serve.sh

For local development, click Launch website.

For Cloud Shell development, click Web Preview > Preview on port 8080.

Web preview screenshot

Confirm that Save to Google Pay works:

  1. Click the loyalty sign-up link.
  2. Complete and submit the sign-up form.
  3. Click Save to Google Pay.

In this step, you integrate a sample Android app with the same APIs that you created for your sample web app. You add the Save to Google Pay button and launch the Google Pay app to save the loyalty pass.

To complete this integration, you should use the following:

  • A local development environment
  • Android Studio

Get the sample app source code

Download the sample app using git with the following commands:

git clone https://github.com/google-pay/loyalty-api-kotlin.git

If you prefer a zip archive, click Download source code.

Browse the code and test the app

  1. Open the project, look through the code, and familiarize yourself with the following files:
    • SignUpRepository
    • Calls the /api/loyalty/create API that you implemented earlier to create a loyalty pass that returns the JWT
    • SignUpActivity (and layout/activity_signup.xml)
    • Handles form submission, calls the SignUpRepository through SignUpViewModel
    • Launches and provides the JWT to the SignUpConfirmationActivity
    • SignUpConfirmationActivity (and layout/content_sign_up_confirmation.xml)
    • Displays the Save to Google Pay button
    • Launches an ACTION_VIEW intent with the Save to Google Pay URL
  2. Click Run > Run ‘app' to start the app and run the project.

Implement SignUpRepository.signUp

The signUp method initiates an HTTP request with the name and email for the loyalty pass, and returns the JWT.

Implement the missing steps with the example below:

loyaltyapidemo/data/SignUpRepository.kt

suspend fun signUp(name: String, email: String): String {
    // Step 1: Call your API to create a loyalty pass.
    val response = executeJsonRequest(
        Request.Method.POST,
        "${BuildConfig.API_HOST}/api/loyalty/create",
        JSONObject(
            mapOf(
                "name" to name,
                "email" to email
            )
        )
    )

    // Step 2: Return the JWT from the token field.
    return response.getString("token")
}

Start the SignUpConfirmationActivity

The SignUpRepository.signUp has already been connected to the Sign Up button for you through SignUpActivity.signUp.setOnClickListener > SignUpViewModel.signUp. All that's left to do is handle the result from SignUpViewModel.signUpResult.

loyaltyapidemo/activity/signup/SignUpActivity.kt

signUpViewModel.signUpResult.observe(
    this@SignUpActivity,
    Observer {
        // ...

        // Step 1: Start SignUpConfirmationActivity with the JWT from the signUpResult.
        val intent = Intent(this, SignUpConfirmationActivity::class.java).apply {
            putExtra("jwt", signUpResult.success!!.jwt)
        }
        startActivity(intent)
    }
)

Add the Save to Google Pay button

  1. Follow the Save to Google Pay button guidelines and download the button assets.
  2. Choose the button language and style for your app, and import them into your project.
  1. Add the Save to Google Pay ImageButton to the confirmation screen.

res/layout/content_sign_up_confirmation.xml

<!-- Step 1: Add the Google Pay image button. -->
<ImageButton
    android:id="@+id/saveButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="32dp"
    android:layout_marginEnd="16dp"
    android:background="#00FFFFFF"
    android:contentDescription="@string/save_to_phone"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/signUpConfirmationMessage"
    app:srcCompat="@drawable/save_to_google_pay_en_light_with_stroke" />

Launch the Save to Google Pay activity

Get the JWT

Get the JWT passed in from the previous activity:

loyaltyapidemo/activity/signup/SignUpConfirmationActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    // Step 1: Read the JWT from the intent.
    val jwt = intent.getStringExtra("jwt")

    // Step 2: Handle the saveButton onClick event.
}

Handle the click event

Handle the save button's onClick event and start an ACTION_VIEW activity with the same URL that you used in the web integration.

loyaltyapidemo/activity/signup/SignUpConfirmationActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    // Step 2: Handle the saveButton onClick event.
    activityBinding.content.saveButton.setOnClickListener {
        val uri = Uri.parse("https://pay.google.com/gp/v/save/$jwt")
        val intent = Intent(Intent.ACTION_VIEW, uri)
        startActivity(intent)
    }
}

Test the integration

Test the integration on either an Android emulator or device.

Congratulations, you integrated the Google Pay Passes API for Loyalty into your sample app, and created a loyalty program and integrated with the Loyalty API to save a loyalty pass!

If you followed the optional steps, you have also did the following:

  • Updated loyalty points on a loyalty pass using the REST APIs
  • Integrated with the Enrollment and Sign-in APIs that enable sign up from Google surfaces, such as Google Pay and Google Maps.
  • Integrated with the Save to Google Pay button to improve the save experience for customers on your web site
  • Integrated the Save to Google Pay button in an Android app.

Learn more

Sign up for an issuer account

To actually integrate with the Loyalty API, sign up for a Google Pay Passes API issuer account.

Customize your loyalty program and passes

To help your customers build a stronger relationship with your brand, see the Pass template.