All Pass verticals have common use cases. For example, all loyalty cards, gift cards, offers, event tickets, boarding passes for flights, and transit passes can be added to the Google Pay App in multiple ways. Select one of the following methods to learn more:
From an Android app
You can use the following methods to add the Save to Google Pay button to your Android app:
Use the Android SDK
The Android API lets you save Passes in Google Pay. When you integrate the Save to Google Pay button in your app, you make it easy for your customers to save their Pass to Google Pay.
The following steps outline how to add the Save to Google Pay button for the Loyalty Pass, although the process is the same for all Passes.
1. Create a class
First, define the LoyaltyClass
. The following example shows a JSON resource that
represents a LoyaltyClass
:
{ "accountIdLabel": "Member Id", "accountNameLabel": "Member Name", "id": "2945482443380251551.ExampleClass1", "issuerName": "Baconrista", "kind": "walletobjects#loyaltyClass", "textModulesData": [ { "header": "Rewards details", "body": "Welcome to Baconrista rewards. Enjoy your rewards for being a loyal customer. " + "10 points for every dollar spent. Redeem your points for free coffee, bacon and more!" } ], "linksModuleData": { "uris": [ { "kind": "walletobjects#uri", "uri": "https://maps.google.com/map?q=google", "description": "Nearby Locations" }, { "kind": "walletobjects#uri", "uri": "tel:6505555555", "description": "Call Customer Service" } ] }, "imageModulesData": [ { "mainImage": { "kind": "walletobjects#image", "sourceUri": { "kind": "walletobjects#uri", "uri": "https://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg", "description": "Coffee beans" } } } ], "messages": [{ "header": "Welcome to Banconrista Rewards!", "body": "Featuring our new bacon donuts.", "kind": "walletobjects#walletObjectMessage" }], "locations": [{ "kind": "walletobjects#latLongPoint", "latitude": 37.424015499999996, "longitude": -122.09259560000001 },{ "kind": "walletobjects#latLongPoint", "latitude": 37.424354, "longitude": -122.09508869999999 },{ "kind": "walletobjects#latLongPoint", "latitude": 37.7901435, "longitude": -122.39026709999997 },{ "kind": "walletobjects#latLongPoint", "latitude": 40.7406578, "longitude": -74.00208940000002 }], "programLogo": { "kind": "walletobjects#image", "sourceUri": { "kind": "walletobjects#uri", "uri": "https://farm8.staticflickr.com/7340/11177041185_a61a7f2139_o.jpg" } }, "programName": "Baconrista Rewards", "rewardsTier": "Gold", "rewardsTierLabel": "Tier", "reviewStatus": "underReview", "hexBackgroundColor": "#ffffff", "heroImage": { "kind": "walletobjects#image", "sourceUri": { "kind": "walletobjects#uri", "uri": "https://farm8.staticflickr.com/7302/11177240353_115daa5729_o.jpg" } } }
2. Create an object
After you create the class, define the LoyaltyObject
, as shown in the following
snippet:
{ "classId": "2945482443380251551.ExampleClass1", "id": "2945482443380251551.ExampleObject1", "accountId": "1234567890", "accountName": "Jane Doe", "barcode": { "alternateText": "12345", "type": "qrCode", "value": "28343E3" }, "textModulesData": [{ "header": "Jane's Baconrista Rewards", "body": "Save more at your local Mountain View store Jane. " + "You get 1 bacon fat latte for every 5 coffees purchased. " + "Also just for you, 10% off all pastries in the Mountain View store." }], "linksModuleData": { "uris": [ { "kind": "walletobjects#uri", "uri": "https://www.baconrista.com/myaccount?id=1234567890", "description": "My Baconrista Account" }] }, "infoModuleData": { "labelValueRows": [{ "columns": [{ "label": "Next Reward in", "value": "2 coffees" }, { "label": "Member Since", "value": "01/15/2013" }] }, { "columns": [{ "label": "Local Store", "value": "Mountain View" }] }], "showLastUpdateTime": "true" }, "loyaltyPoints": { "balance": { "string": "5000" }, "label": "Points", "pointsType": "points" }, "messages": [{ "header": "Jane, welcome to Banconrista Rewards!", "body": "Thanks for joining our program. Show this message to " + "our barista for your first free coffee on us!" }], "state": "active" }
3. Encode an unsigned JWT
After you create the object, encode the LoyaltyClass
and LoyaltyObject
into an unsigned JWT, as shown in the following snippet:
{ "iss": "example_service_account@developer.gserviceaccount.com", "aud": "google", "typ": "savetoandroidpay", "iat": 1368029586, "payload": { "eventTicketClasses": [{ ... //Event ticket Class JSON }], "eventTicketObjects": [{ ... //Event ticket Object JSON }], "flightClasses": [{ ... //Flight Class JSON }], "flightObjects": [{ ... //Flight Object JSON }], "giftCardClasses": [{ ... //Gift card Class JSON }], "giftCardObjects": [{ ... //Gift card Object JSON }], "loyaltyClasses": [{ ... //Loyalty Class JSON }], "loyaltyObjects": [{ ... //Loyalty Object JSON }], "offerClasses": [{ ... //Offer Class JSON }], "offerObjects": [{ ... //Offer Object JSON }], "transitClasses": [{ ... //Transit Class JSON }], "transitObjects": [{ ... //Transit Object JSON }] }, "origins": ["http://baconrista.com", "https://baconrista.com"] }
4. Choose a request format to use
The Android SDK lets you make requests with one of the following formats:
- The
savePasses
method uses a JSON string payload. - The
savePassesJwt
method uses a JWT string token payload.
For more details on how to make a request, see Call the Android SDK.
savePasses
Requests to the savePasses
method have a JSON string payload. This means you can
directly use the JSON for the object created in step 3.
You can make requests to Save to Google Pay for classes and objects that already exist or that are inserted as part of the save process. You can also save multiple passes in the same request if the pass vertical supports that functionality. You can't upsert classes and objects that already exist.
In order to improve security, some fields are considered sensitive, and in these cases you can't save passes that already exist by just specifying the object ID field. You can only save objects that already exist if the sensitive fields in the request match the fields of the objects that already exist. The following fields are considered sensitive:
object.barcode.value
object.smartTapRedemptionValue
savePassesJwt
Requests to the savePassesJwt
method have a JWT string token payload. To create the
JWT, sign the object from step 3 with your OAuth 2.0 service
account private key. The following snippets show how to encode a JWT in various languages:
Java
WobCredentials credentials = null; WobUtils utils = null; // Instantiate the WobUtils class which contains handy functions // Wob utils can be found in the quickstart sample try { credentials = new WobCredentials( ServiceAccountEmailAddress, ServiceAccountPrivateKeyPath, ApplicationName, IssuerId); utils = new WobUtils(credentials); } catch (GeneralSecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // Add valid domains for the Save to Wallet button List<String> origins = new ArrayList<String>(); origins.add("http://baconrista.com"); origins.add("https://baconrista.com"); origins.add(req.getScheme() + "://" + req.getServerName() + ":" + req.getLocalPort()); //Generate Objects and Classes here //........ WobPayload payload = new WobPayload(); payload.addObject({WalletObject/WalletClass}); // Convert the object into a Save to Android Pay Jwt String jwt = null; try { jwt = utils.generateSaveJwt(payload, origins); } catch (SignatureException e) { e.printStackTrace(); }
PHP
$requestBody = [ "iss"=> SERVICE_ACCOUNT_EMAIL_ADDRESS, "aud" => "google", "typ" => "savetoandroidpay", "iat"=> time(), "payload" => { "eventTicketClasses" => [ ], # Event ticket classes "eventTicketObjects" => [ ], # Event ticket objects "flightClasses" => [ ], # Flight classes "flightObjects" => [ ], # Flight objects "giftCardClasses" => [ ], # Gift card classes "giftCardObjects" => [ ], # Gift card objects "loyaltyClasses" => [ ], # Loyalty classes "loyaltyObjects" => [ ], # Loyalty objects "offerClasses" => [ ], # Offer classes "offerObjects" => [ ], # Offer objects "transitClasses" => [ ], # Transit classes "transitObjects" => [ ] # Transit objects }, "origins" => ["http://baconrista.com", "https://baconrista.com"] ] // Generate the Save to Android Pay Jwt echo $jwt = $assertObj->makeSignedJwt($requestBody, $client);
Python
jwt = { 'iss': config.SERVICE_ACCOUNT_EMAIL_ADDRESS, 'aud': 'google', 'typ': 'savetoandroidpay', 'iat': int(time.time()), 'payload': { 'webserviceResponse': { 'result': 'approved', 'message': 'Success.' }, 'eventTicketClasses': [], # Event ticket classes 'eventTicketObjects': [], # Event ticket objects 'flightClasses': [], # Flight classes 'flightObjects': [], # Flight objects 'giftCardClasses': [], # Gift card classes 'giftCardObjects': [], # Gift card objects 'loyaltyClasses': [], # Loyalty classes 'loyaltyObjects': [], # Loyalty objects 'offerClasses': [], # Offer classes 'offerObjects': [], # Offer objects 'transitClasses': [], # Transit classes 'transitObjects': [] # Transit objects }, 'origins' : ['http://baconrista.com', 'https://baconrista.com'] } // Generate the Save to Android Pay Jwt signer = crypt.Signer.from_string(app_key) signed_jwt = crypt.make_signed_jwt(signer, jwt) response = webapp2.Response(signed_jwt)
You can make requests to Save to Google Pay for classes and objects that already exist or that are inserted as part of the save process. You can also save multiple passes in the same request if the pass vertical supports that functionality. You can't upsert classes and objects that already exist. Skinny JWTs can be used as long as the classes and objects already exist.
5. Call the Android SDK
First, use the getPayApiAvailabilityStatus
method to check whether the
savePasses
or savePassesJwt
methods are available, as shown in the
following example:
import com.google.android.gms.common.api.UnsupportedApiCallException; import com.google.android.gms.pay.Pay; import com.google.android.gms.pay.PayApiAvailabilityStatus; import com.google.android.gms.pay.PayClient; … PayClient payClient = Pay.getClient(this); payClient // Use PayClient.RequestType.SAVE_PASSES_JWT for the savePassesJwt API .getPayApiAvailabilityStatus(PayClient.RequestType.SAVE_PASSES) .addOnSuccessListener( status -> { switch (status) { case PayApiAvailabilityStatus.AVAILABLE: // You can call the savePasses API or savePassesJwt API ... break; case PayApiAvailabilityStatus.NOT_ELIGIBLE: default: // We recommend to either: // 1) Hide the save button // 2) Fall back to a different Save Passes integration (e.g. JWT link) // Note however that the user *will* only be able to access their // passes on web // A not eligible user might become eligible in the future. ... break; } }) .addOnFailureListener( exception -> { if (exception instanceof UnsupportedApiCallException) { // Google Play Services too old. We could not check API availability or // user eligibility. We recommend to either: // 1) Fall back to a different Save Passes integration (e.g. JWT link) // Note however that the user *may* only be able to access their // passes on web // 2) Hide the save button ... } else { // Very old version of Google Play Services or unexpected error! ... } });
If the API is available, call the savePasses
or savePassesJwt
method
when the user taps on the Save to Google Pay button.
savePasses
private static final int SAVE_TO_GOOGLE_PAY = 1000; … String jsonString = … // Build or fetch JSON request PayClient payClient = Pay.getClient(this); payClient.savePasses(jsonString, this, SAVE_TO_GOOGLE_PAY);
savePassesJwt
private static final int SAVE_TO_GOOGLE_PAY = 1000; … String jwtString = … // Fetch JWT from a secure server PayClient payClient = Pay.getClient(this); payClient.savePassesJwt(jwtString, this, SAVE_TO_GOOGLE_PAY);
This call triggers the save flow. After the flow finishes, your app resolves the result with
onActivityResult
. In your Activity, this receiver needs to be defined similar to the
following:
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // `data` will only have information in the `SAVE_ERROR` case if (requestCode == SAVE_TO_GOOGLE_PAY) { switch (resultCode) { case Activity.RESULT_OK: // Save successful ... break; case Activity.RESULT_CANCELED: // Save canceled ... break; case PayClient.SavePassesResult.API_ERROR: // API error - this should not happen if getPayApiAvailabilityStatus is // used correctly ... break; case PayClient.SavePassesResult.SAVE_ERROR: // Save error - check EXTRA_API_ERROR_MESSAGE to debug the issue // Most save errors indicate an error in the app fingerprint or the Json // request payload. In most cases prompting the user to try again will not // help. if (data != null && !isEmpty(data.getStringExtra(PayClient.EXTRA_API_ERROR_MESSAGE))) { ... } else { // Unexpected! A save error should always have a debug message associated // with it ... } break; case PayClient.SavePassesResult.INTERNAL_ERROR: default: // Internal error - prompt the user to try again, if the error persists // disable the button ... break; } } else { ... } }
6. Add the Save to Google Pay button to your UI
Google Pay provides an Android SDK button for you to integrate into your app. The button assets are available in the Brand guidelines.
This toolkit contains vector images of the buttons.
To incorporate a button into your application, copy over the button image from the toolkit into
the res
folder of your application and add the following code to your Android layout
file. Note that each button requires its unique contentDescription
string and
minWidth
value in addition to the correct value for src
.
<ImageButton android:layout_width="match_parent" android:layout_height="48dp" android:minWidth="200dp" android:clickable="true" android:src="@drawable/s2ap" />
The layout_height
for the button is 48 dp and minWidth
must be
200 dp.
Use the JWT link and intent method
Use the following steps to save your Pass to Google Pay from your app:
- Complete the steps in Add the Save to Google Pay button to your email or SMS.
Use an
ACTION_VIEW
intent to open the deep link from the Save to Google Pay button.Make sure that the button that triggers the intent uses the Brand guidelines.
The following is an example flow summary:
- At some time before a Pass is saved, a class is created on the backend with the REST API.
- When the end user asks to saves a pass, your server backend sends a
JWT
to your Android client app that represents an object. - Your Android client app includes a Save to Google Pay button that follows our
brand guidelines.
When clicked, it opens an
ACTION_VIEW
intent to a URI that includes the JWT in its path. Here's an example:https://pay.google.com/gp/v/save/{jwt_generated}
Use the JWT POST request method
The JWT POST
request method is an alternative method to create flight or event
ticket classes and objects for Android apps. It's used when it's difficult to implement the
backend work required to create and insert a class before an object is saved. This method is most
useful for event tickets and boarding passes, which are Passes that can have many classes created
over time. Its flow is summarized as follows:
- When the end user checks in to their flight, or redeems an event ticket, your server backend renders a JWT to your Android client app that includes both the class and object.
- Your Android client app includes a Save to Google button that follows our brand guidelines. When you click the button, the following happens:
- A
POST
request sends the JWT to a Google endpoint through HTTPS. - A URI from the resulting HTTP response body is sent in return, which should then be used
to open an
ACTION_VIEW
intent.
The JWT POST
request method also requires an
API key. This is appended as a
query parameter to the REST API call.
Class creation
A new class is only created on our backend when presented with a class.id
that
hasn't been saved in the past. Therefore, while you might potentially pass the class details to
Google through the JWT multiple times, the backend recognizes that the class is already saved, and
it doesn't create new ones every time a boarding pass is saved.
Class updates
After the first boarding pass, the object is saved along with the class. You can use the
class.id
as expected with our REST API to perform ADDMESSAGE
,
GET
, LIST
, PATCH
, and UPDATE
operations as
expected.
To change class details, you must use the Class Update API. If you create a class with
class.id
=XYZ and some other class details, and you attempt to create a class
later with class.id
=XYZ but with different class details, we still maintain
the original class and won't apply any changes.
JWT format
The format for the JWT you send is described in detail by our reference documentation about
Google Pay API for Passes JWT.
For this payload
, you pass one entry for objects, which represents the object you
want to create, and one entry for classes, which contains the class that you created.
HTTP request
You can use the INSERT
method to
insert classes and objects specified in a JWT. The API Key must be set as a query parameter.
JWT INSERT method
Given a JWT, the INSERT
method inserts the classes and objects specified in a
JWT. If successful, it returns a 200 HTTP response.
HTTP request
POST https://walletobjects.googleapis.com/walletobjects/v1/jwt/
Authorization
This request requires no authorization. However, the JWT must be signed with RSA-SHA256. The signing key is the OAuth service account-generated key.
Request body
In the request body, supply data with the following structure:
{ “jwt” : string }
Response body
When successful, this method returns a response body with the following structure:
{ "saveUri": string, "resources": { "eventTicketClasses": [ eventTicketClass resource, ... ], "eventTicketObjects": [ eventTicketObject resource, ... ], "flightClasses": [ flightClass resource, ... ], "flightObjects": [ flightObject resource, ... ], "giftCardClasses": [ giftCardClass resource, ... ], "giftCardObjects": [ giftCardObject resource, ... ], "loyaltyClasses": [ loyaltyClass resource, ... ], "loyaltyObjects": [ loyaltyObject resource, ... ], "offerClasses": [ offerClass resource, ... ], "offerObjects": [ offerObject resource, ... ], "transitClasses": [ transitClass resource, ... ], "transitObjects": [ transitObject resource, ... ] } }
A saveUri
is a URI that, when opened, allows the end user to save the
objects identified in the JWT to their Google account. This URI is only valid a week after it's
returned.
See the JWT endpoint reference for more details.
Flow diagrams
See Typical API flows for flow diagrams.