Get Started with Offers on the Web

The Save to Android Pay API faciliates saving and managing offers in Android Pay. By integrating the Save to Android Pay button on your website or in your app, you make it easy for your customers to save their offers to Android Pay. Once saved, these offers can be configured for redemption and continually updated with relevant, engaging content.

This "Get Started" guide provides the basic steps required to integrate the Save to Android Pay button on your website. For more details on other key use-cases refer to the Concepts guide.

Overview

Saving a offer to Android Pay requires the user to explicitly click the Save to Android Pay button that you integrate. When the user clicks the button, a JSON Web Token (JWT) that represents their offer is sent to our servers. Our servers then create a OfferObject resource based on this JWT and connect it to the user's account.

There are three steps to integrating the Save to Android Pay button on your website:

  1. Define an OfferClass that your OfferObjects will extend.
  2. Generate a JWT that represents the OfferObject.
  3. Include the Save to Android Pay button on your web page.

Note: Before continuing, make sure you have set up your project and have access to the Save to Android Pay API. If not, follow the instructions in the Basic Setup guide.

Integrating the Save to Android Pay Button

Define the OfferClass

The first step to integrating is to define the OfferClass.

Once you have defined the OfferClass, insert it by making a POST request to the following REST URI:

https://www.googleapis.com/walletobjects/v1/offerClass

Note: Add the strict=true parameter to the REST URI to enable strict error parsing and catch additional errors, such as duplicate ID fields: https://www.googleapis.com/walletobjects/v1/offerClass?strict=true

The following is an example of a OfferClass resource. Each code sample demonstrates defining the OfferClass resource and inserting it.

Resource

{
  "kind": "walletobjects#offerClass",
  "id": "#{issuerId}.#{classId}",
  "issuerName": "Baconrista Coffee",
  "issuerData": {
  "kind": "walletobjects#typedValue"
  },
  "title": "20% off on one bacon fat latte",
  "redemptionChannel": "both",
  "provider": "Baconrista Deals",
  "titleImage": {
    "kind": "walletobjects#image",
    "sourceUri": {
      "kind": "walletobjects#uri",
      "uri": "http://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
    }
  },
  "allowMultipleUsersPerObject"=>true,
  "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
  }],
  "reviewStatus": "underReview",
  "review": {
    "comments": "Real auto approval by system"
  },
  "textModulesData": [
    {
      "header": "Details",
      "body": "20% off one cup of coffee at all Baconrista Coffee locations. " +
                "Only one can be used per visit."
    },
    {
      "header": "About Baconrista",
      "body": "Since 2013, Baconrista Coffee has been committed to making high " +
                "quality bacon coffee. Visit us in our stores or online at www.baconrista.com"
    }
  ],
  "linksModuleData": {
    "uris": [
      {
        "kind": "walletobjects#uri",
        "uri": "http://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": "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
          "description": "Coffee beans"
        }
      }
    }
  ],
  "hexBackgroundColor": "#ffffff",
  "heroImage": {
   "kind": "walletobjects#image",
   "sourceUri": {
     "kind": "walletobjects#uri",
     "uri": "http://farm8.staticflickr.com/7302/11177240353_115daa5729_o.jpg"
    }
  }
}

Java

// Define the Image Module Data
List<ImageModuleData> imageModuleData = new ArrayList<ImageModuleData>();

ImageModuleData image = new ImageModuleData().setMainImage(
    new Image().setSourceUri(
        new Uri().setUri("http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg")));

imageModuleData.add(image);

// Define Links Module Data
List<Uri> uris = new ArrayList<Uri>();
Uri uri1 = new Uri().setDescription("Nearby Locations").setUri("http://maps.google.com/?q=google");
Uri uri2 = new Uri().setDescription("Call Customer Service").setUri("tel:6505555555");

uris.add(uri1);
uris.add(uri2);

LinksModuleData linksModuleData = new LinksModuleData().setUris(uris);

// Define Text Areas
List<TextModuleData> textModulesData = new ArrayList<TextModuleData>();

TextModuleData details = new TextModuleData().setHeader("Details").setBody(
    "20% off one cup of coffee at all Baconrista Coffee locations.  Only one can be used per visit.");
TextModuleData finePrint = new TextModuleData().setHeader("About Baconrista").setBody(
    "Since 2013, Baconrista Coffee has been committed to making high quality bacon coffee. Visit us in our stores or online at www.baconrista.com");

textModulesData.add(details);
textModulesData.add(finePrint);

// Define Geofence locations
List<LatLongPoint> locations = new ArrayList<LatLongPoint>();
locations.add(new LatLongPoint().setLatitude(37.422601).setLongitude(
    -122.085286));
locations.add(new LatLongPoint().setLatitude(37.424354).setLongitude(
    -122.09508869999999));
locations.add(new LatLongPoint().setLatitude(40.7406578).setLongitude(
    -74.00208940000002));

OfferClass wobClass = new OfferClass()
    .setId('2945482443380251551.ExampleOfferClass1')
    .setIssuerName("Baconrista Coffee")
    .setTitle("20% off one bacon fat latte")
    .setProvider("Baconrista Deals")
    .setTitleImage(
        new Image().setSourceUri(new Uri()
            .setUri("http://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg")))
    .setRedemptionChannel("both")
    .setReviewStatus("underReview")
    .setLinksModuleData(linksModuleData)
    .setImageModulesData(imageModuleData)
    .setTextModulesData(textModulesData)
    .setLocations(locations).setAllowMultipleUsersPerObject(true);

OfferClass response = client.offerclass().insert(wobClass).execute();

PHP

// A list of locations at which the Wallet Class can be used.
$locations = array(
    array(
        'kind' => 'walletobjects#latLongPoint',
        'latitude' => 37.424015499999996,
        'longitude' => -122.09259560000001
    ),
    array(
        'kind' => 'walletobjects#latLongPoint',
        'latitude' => 37.424354,
        'longitude' => -122.09508869999999
    ),
    array(
        'kind' => 'walletobjects#latLongPoint',
        'latitude' => 37.7901435,
        'longitude' => -122.39026709999997
    ),
    array(
        'kind' => 'walletobjects#latLongPoint',
        'latitude' => 40.7406578,
        'longitude' => -74.00208940000002
    )
);
// Source uri of title image.
$uriTitleImageInstance = new Google_Service_Walletobjects_Uri();
$imageTitleImageInstance = new Google_Service_Walletobjects_Image();
$uriTitleImageInstance->setUri(
    'http://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg'
);
$imageTitleImageInstance->setSourceUri($uriTitleImageInstance);
// Define text module data.
$textModulesData = array(
    array(
        'header' => 'Details',
        'body' => '20% off one cup of coffee at all Baconrista Coffee locations. ' .
            'Only one can be used per visit.'
    ),
    array(
        'header' => 'About Baconrista',
        'body' => 'Since 2013, Baconrista Coffee has been committed to making high ' .
            'quality bacon coffee. Visit us in our stores or online at www.baconrista.com'
    )
);
// Define links module data.
$linksModuleData = new Google_Service_Walletobjects_LinksModuleData();
$uris = array (
    array(
        'uri' => 'http://maps.google.com/map?q=google',
        'kind' => 'walletobjecs#uri',
        'description' => 'Nearby Locations'
    ),
    array(
        'uri' => 'tel:6505555555',
        'kind' => 'walletobjecs#uri',
        'description' => 'Call Customer Service'
    )
);
$linksModuleData->setUris($uris);

$uriModuleImageInstance = new Google_Service_Walletobjects_Uri();
$uriModuleImageInstance->setUri(
    'http://farm8.staticflickr.com/7401/11177116434_d8e600bba6_o.jpg'
);
$uriModuleImageInstance->setDescription('Coffee beans');
$imageModuleImageInstance = new Google_Service_Walletobjects_Image();
$imageModuleImageInstance->setSourceUri($uriModuleImageInstance);
$imagesModuleData = new Google_Service_Walletobjects_ImageModuleData();
$imagesModuleData->setMainImage($imageModuleImageInstance);
$imagesModuleDataArr = array ($imagesModuleData);

// Create wallet class.
$wobClass = new Google_Service_Walletobjects_OfferClass();
$offerClass->setId('2945482443380251551.OfferClass');
$wobClass->setIssuerName('Baconrista Coffee');
$wobClass->setTitle('20% off on one bacon fat latte');
$wobClass->setProvider('Baconrista Deals');
$wobClass->setTitleImage($imageTitleImageInstance);
$wobClass->setLinksModuleData($linksModuleData);
$wobClass->setTextModulesData($textModulesData);
$wobClass->setImageModulesData($imagesModuleDataArr);
$wobClass->setRedemptionChannel('both');
$wobClass->setReviewStatus('underReview');
$wobClass->setLocations($locations);
$wobClass->setAllowMultipleUsersPerObject(true);

$service->offerclass->insert($wobClass);

Python

api_object = generate_offer_class(
      '1234567', 'ExampleOfferClass')
api_request = service.offerclass().insert(body=api_object)
api_response = api_request.execute()

def generate_offer_class(issuer_id, class_id):
  offer_class = {
      'kind': 'walletobjects#offerClass',
      'id': '%s.%s' % (issuer_id, class_id),
      'issuerName': 'Baconrista Coffee',
      'issuerData': {
          'kind': 'walletobjects#typedValue'
      },
      'title': '20% off one bacon fat latte',
      'redemptionChannel': 'both',
      'provider': 'Baconrista Deals',
      'titleImage': {
          'kind': 'walletobjects#image',
          'sourceUri': {
              'kind': 'walletobjects#uri',
              'uri': 'http://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg'
          }
      },
      'allowMultipleUsersPerObject': True,
      '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
      }],
      'reviewStatus': 'underReview',
      'review': {
          'comments': 'Real auto approval by system'
      },
      'textModulesData': [{
        'header': 'Details',
        'body': '20% off one cup of coffee at all Baconrista Coffee locations. ' +
                'Only one can be used per visit.'
       },{
        'header': 'About Baconrista',
        'body': 'Since 2013, Baconrista Coffee has been committed to making high ' +
                'quality bacon coffee. Visit us in our stores or online at www.baconrista.com'
      }],
      'linksModuleData': {
        'uris': [
          {
            'kind': 'walletobjects#uri',
            'uri': 'http://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':  'http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg',
              'description': 'Coffee beans'
            }
          }
        }
      ],
    }
  return offer_class

Note: For a complete list of all OfferClass fields see OfferClass reference.

Warning: You must set the reviewStatus field from draft to underReview when you believe the class is ready for review. The class will not be reviewed if you do not set the class to underReview. Unreviewed classes (classes set to draft) will not be visible to the public (only to trusted testers).

Generate the JWT for the OfferObject

On your server, define the OfferObject resource to be encoded in a JWT. Once the resource is defined, you do not need to make aPOST request.

Resource

{
  "kind": "walletobjects#offerObject",
  "classId": "2945482443380251551.OfferClass",
  "id": "2945482443380251551.OfferObject",
  "version": "1",
  "state": "active",
  "issuerData": {
    "kind": "walletobjects#typedValue"
  },
  "barcode": {
    "kind": "walletobjects#barcode",
    "type": "upcA",
    "value": "123456789012",
    "alternateText": "12345"
  },
  "validTimeInterval": {
    "kind": "walletobjects#timeInterval",
    "start": "2013-06-12T23:20:50.52Z",
    "end": "2013-12-12T23:20:50.52Z"
  }
}

Java

// Define Barcode
Barcode barcode = new Barcode().setType("upcA").setValue("123456789012")
    .setAlternateText("12345");

// Define Wallet Object
OfferObject object = new OfferObject()
	.setClassId('2945482443380251551.ExampleOfferClass1')
    .setId('2945482443380251551.ExampleOfferObject1')
    .setVersion(1L)
    .setBarcode(barcode)
    .setValidTimeInterval(new TimeInterval().setEnd(new DateTime().setDate(new com.google.api.client.util.DateTime(
            new Date().getTime() + 263000000000L))))
    .setState("active");

PHP

$barcode = new Google_Service_Walletobjects_Barcode();
$barcode->setType('upcA');
$barcode->setValue('123456789012');
$barcode->setAlternateText('12345');

$validTimeInterval = new Google_TimeInterval();
$startDateTime = new Google_DateTime();
$startDateTime->setDate('2013-06-12T23:20:50.52Z');
$validTimeInterval->setStart($startDateTime);
$endDateTime = new Google_DateTime();
$endDateTime->setDate('2013-12-12T23:20:50.52Z');
$validTimeInterval->setEnd($endDateTime);

// Create wallet object.
$offerObject = new Google_Service_Walletobjects_OfferObject();
$offerObject->setClassId('2945482443380251551.OfferClass');
$offerObject->setId('2945482443380251551.OfferObject');
$offerObject->setVersion(1);
$offerObject->setBarcode($barcode);
$offerObject->setValidTimeInterval($validTimeInterval);
$offerObject->setState('active');

Python

offer_object = {
      'kind': 'walletobjects#offerObject',
      'classId': '1234567.ExampleOfferClass',
      'id': '1234567.ExampleOfferObject',
      'version': '1',
      'state': 'active',
      'issuerData': {
          'kind': 'walletobjects#typedValue'
      },
      'barcode': {
          'kind': 'walletobjects#barcode',
          'type': 'upcA',
          'value': '123456789012',
          'alternateText': '12345'
      }
  }

Using your OAuth 2.0 service account private key, encode the OfferObject in a JWT. The following is an unencoded JWT and sample code to generate the JWT in different languages. Refer to Save to Android Pay JWT for an explanation of all the fields in the unencoded JWT.

Protocol

  
{
  "iss": "example_service_account@developer.gserviceaccount.com",
  "aud": "google",
  "typ": "savetoandroidpay",
  "iat": 1368029586,
  "payload": {
    "loyaltyObjects": [{
      ... //Loyalty Object JSON
      }],
    "offerObjects": [{
      ... //Offer Object JSON
      }],
    "loyaltyClasses": [{
      ... //Loyalty Class JSON
      }],
    "offerClasses": [{
      ... //Offer Class JSON
      }]
    }]
  },
  "origins": ["http://baconrista.com", "https://baconrista.com"]
}

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" => {
    "loyaltyObjects" => [ ],  #Loyalty objects
    "offerObjects" => [ ],  #Offer objects
    "loyaltyClasses" => [ ],  #Loyalty classes
    "offerClasses" => [ ]  #Offer classes
  },
  "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.'
    },
    'loyaltyClasses': [], # Loyalty classes
    'loyaltyObjects': [], # Loyalty objects
    'offerClasses': [], # Offer classes
    'offerObjects': []  # Offer 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)

The safe length of an encoded JWT is 1800 characters. Your JWTs should remain below this limit. Objects encoded in JWTs should be small, containing only data that is specific to the user. Try to keep most data in the object's class, creating it before making the JWT. For larger objects that do not fit the limit, consider creating the object in the S2AP API and sending only the object ID in the JWT.

Include the Save to Android Pay Button

Include the following script on the page where you want to display the Save to Android Pay button:

<script src="https://apis.google.com/js/plusone.js" type="text/javascript"></script>

Next, insert the g:savetoandroidpay namespace tag that defines the placement and various attributes of the Save to Android Pay button. Be sure to include the JWT generated previously.

<g:savetoandroidpay jwt="{JWT generated above}" onsuccess="successHandler"
onfailure="failureHandler" size="small" theme="light" ></g:savetoandroidpay>
That's it! Now you should see the Save to Android Pay button rendered on your webpage.

Saving the offer

When desktop visitors to your site click the Save to Android Pay button, a rollover frame will appear with a preview of their offer. Here they can confirm the save, after which the card will appear in their Android Pay app. The user can switch which Google account to associate with their offer by clicking on the profile picture on the top right corner of the rollover frame.

On mobile, pressing the Save to Android Pay button will take the user to the Android Pay app to complete the save, if Android Pay is installed. Otherwise, the button will take the user to a mobile web page to complete the save.

Note: If a user is not currently logged into their Google Account, they will be taken to another window to authenticate.

Viewing the saved offer

Open Android Pay to view your saved offers. Remember to use the same Google Account in Android Pay that you used to save the offer.

Learn More

To discover more about what offers can do, see Concepts.

References

For more detailed technical information, see the API Reference.

Send feedback about...

Save to Android Pay API