Integrate the Google Wallet API to digitize passes on Android

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.: parking card, gym membership card). 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 defining the JSON for a pass object, and include the "Add to Google Wallet" button to your Android app, which will allow your users to save the pass to their Google Wallet.

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

Prerequisites

  • A computer with Android Studio installed.
  • Ability to create and run a project in Android Studio.
  • git installed.

What You'll Learn

  • How to add the Google Wallet SDK to your Android app.
  • How to check if the Wallet API is available.
  • How to implement a UI, allowing the user to add a pass to their Google Wallet.

2. Get set up

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"
            }
        ]
    }
}

Clone the git repo

Clone the git repo, which contains the Android project you will be editing, using the following command:

git clone -b wallet-lab git@github.com:google-pay/android-quickstart.git

Open in Android Studio

Open Android Studio, choose "Open" from the welcome screen, and select the kotlin folder in the newly cloned repo to open as a project.

3. Adding the Google Wallet SDK to your app

The Android project contains an empty activity which we will shortly edit, but first let's add the Google Wallet SDK as a dependency. In Android Studio, select the module-level "build.gradle" script, and add the following lines to the dependencies section:

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-pay:16.0.3'
}

Once changed, click the Gradle "Sync Project" button at the top-right of Android Studio.

4. Adding the "Add to Google Wallet" button

Next you'll add the "Add to Google Wallet" button to the activity. The assets for the button have already been added to the project for you, you just need to add it to your layout file. To do that, we recommend creating a separate layout with the button:

add_to_google_wallet_button.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:clickable="true"
    android:focusable="true"
    android:layout_width="match_parent"
    android:layout_height="48sp"
    android:background="@drawable/add_to_google_wallet_button_background_shape"
    android:contentDescription="@string/add_to_google_wallet_button_content_description">
    <ImageView
        android:layout_width="227dp"
        android:layout_height="26dp"
        android:layout_gravity="center"
        android:duplicateParentState="true"
        android:src="@drawable/add_to_google_wallet_button_foreground"/>
</FrameLayout>

This is how the button looks like:

And then, include it in your layout file:

<include
    android:id="@+id/addToGoogleWalletButton"
    layout="@layout/add_to_google_wallet_button"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_marginTop="10dp"/>

5. Checking if the API is available

Now it's time for some coding. Open up the file where you'll make calls against the Google Wallet API in Android Studio (the CheckoutActivity.kt file if you are using the sample application), and instantiate a PayClient object as a member of your class. This object helps you make calls against the Google Wallet API. The getClient method receives the context of the instantiating class, in this case, the calling activity.

private lateinit var walletClient: PayClient

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

    walletClient = Pay.getClient(this)
    ...
}

Next, ensure that the Google Wallet SDK and app are available on the device. Create a new method in your class (eg.: fetchCanUseGoogleWalletApi) to determine whether the Google Wallet API is available, and another one to react to result by showing the "Add to Google Wallet" button if the API is available:

private fun fetchCanUseGoogleWalletApi() {
    walletClient
        .getPayApiAvailabilityStatus(PayClient.RequestType.SAVE_PASSES)
        .addOnSuccessListener { status ->
            if (status == PayApiAvailabilityStatus.AVAILABLE)
                layout.passContainer.visibility = View.VISIBLE
        }
        .addOnFailureListener {
             // Hide the button and optionally show an error message
        }
}

Finally, call the fetchCanUseGoogleWalletApi method in the onCreate method to determine whether the Google Wallet API is available:

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

    ...
    fetchCanUseGoogleWalletApi()
}

You should now see the "Add to Google Wallet" button in your UI when you run the app:

6. Adding a pass to Google Wallet

Now that you have verified that the Google Wallet API is available, you can prompt your users to add passes to their Google Wallet. Before you do that, take a moment to review the pass created in this workshop:

This layout uses the flexibility of generic passes to create an object that works as an identity badge, and at the same time, supports a challenge that prompts participants to collect points during the event. This is how the generic object looks like:

{
    "id": "999999.d1fa-4cca1...",
    "classId": "999999.a92b-129f...",
    "genericType": "GENERIC_TYPE_UNSPECIFIED",
    "hexBackgroundColor": "#4285f4",
    "logo": { ... },
    "cardTitle": { ... },
    "subheader": { ... },
    "header": { ... },
    "barcode": { ... },
    "heroImage": { ... },
    "textModulesData": []
}

To insert the object, the API expects an unsigned JWT body wrapping the new element(s):

{
    "iss": <owner-email-address>,
    "aud": "google",
    "typ": "savetowallet",
    "iat": <unix-time>,
    "origins": [],
    "payload": {
        "genericObjects": [],
        "genericClasses": [],
        ...
    }
}

Put it all together (using the values you obtained earlier when creating a temporary issuer account) and assign the resulting object to a variable in your class:

private val issuerEmail = "<insert-your-issuer-email-address>"
private val issuerId = "<insert-your-issuer-id>"
private val passClass = "<insert-your-class-id>"
private val passId = UUID.randomUUID().toString()

private val newObjectJson = """
    {
      "iss": "$issuerEmail",
      "aud": "google",
      "typ": "savetowallet",
      "iat": ${Date().time / 1000L},
      "origins": [],
      "payload": {
        "genericObjects": [
          {
            "id": "$issuerId.$passId",
            "classId": "$passClass",
            "genericType": "GENERIC_TYPE_UNSPECIFIED",
            "hexBackgroundColor": "#4285f4",
            "logo": {
              "sourceUri": {
                "uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/pass_google_logo.jpg"
              }
            },
            "cardTitle": {
              "defaultValue": {
                "language": "en",
                "value": "Google I/O '22  [DEMO ONLY]"
              }
            },
            "subheader": {
              "defaultValue": {
                "language": "en",
                "value": "Attendee"
              }
            },
            "header": {
              "defaultValue": {
                "language": "en",
                "value": "Alex McJacobs"
              }
            },
            "barcode": {
              "type": "QR_CODE",
              "value": "$passId"
            },
            "heroImage": {
              "sourceUri": {
                "uri": "https://storage.googleapis.com/wallet-lab-tools-codelab-artifacts-public/google-io-hero-demo-only.jpg"
              }
            },
            "textModulesData": [
              {
                "header": "POINTS",
                "body": "${Random.nextInt(0, 9999)}",
                "id": "points"
              },
              {
                "header": "CONTACTS",
                "body": "${Random.nextInt(1, 99)}",
                "id": "contacts"
              }
            ]
          }
        ]
      }
    }
    """

Finally, go back to your onCreate method and add a click handler to your "Add to Google Wallet" button to initiate the operation using the savePasses method in the client:

private val addToGoogleWalletRequestCode = 1000

private lateinit var layout: ActivityCheckoutBinding
private lateinit var addToGoogleWalletButton: View

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

    // Use view binding to access the UI elements
    layout = ActivityCheckoutBinding.inflate(layoutInflater)
    setContentView(layout.root)

    addToGoogleWalletButton = layout.addToGoogleWalletButton.root
    addToGoogleWalletButton.setOnClickListener {
        walletClient.savePasses(newObjectJson, this, addToGoogleWalletRequestCode)
    }

    ...
}

With that, you have defined the JSON for the new object using the issuer ID and class ID created earlier. When the user clicks the "Add to Google Wallet" button, payClient.savePasses is called, and prompts them to add the new pass object to their Google Wallet:

7. Handling the result

You're almost done. Lastly, handle the result of the operation either being successful or not. Override the onActivityResult method to contain the following code:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == addToGoogleWalletRequestCode) {
        when (resultCode) {
            RESULT_OK ->
                // Pass saved successfully. Consider informing the user.
            RESULT_CANCELED -> {
                // Save canceled
            }

            PayClient.SavePassesResult.SAVE_ERROR -> data?.let { intentData ->
                val errorMessage = intentData.getStringExtra(PayClient.EXTRA_API_ERROR_MESSAGE)
                // Handle error. Consider informing the user.
            }

            else -> {
                // Handle unexpected (non-API) exception
            }
        }
    }
}

With this code you are handling the pass being added successfully, the user cancelling the action, and also any errors that may occur. Go ahead and run your application once again to confirm that you can add the pass and handle the result as expected.

8. Congratulations

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

Learn more

Take a look at a complete integration in the sample application in GitHub to see a complete example.

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 and authorize your Android application to issue calls against the API. Check out the documentation to learn more.