Click here to see your recently viewed pages and most viewed pages.
Hide

Offers Tutorial

The Wallet Objects API allows you to manipulate Wallet Objects, enabling you to engage with users through Google Wallet. This tutorial will walk you through interacting with Offers Objects.

Concepts

There are three essential pieces to an offers solution: Your server containing a web service, Google Wallet where offers are stored, and the Google Wallet app used by users to access their offers.

You interact with Google Wallet using either the Wallet Objects REST API or the Save to Google API. For example, you can add a Save to Google button on your site to enable a user to save an offer to Google Wallet.

Before you can share any information with Google Wallet, you must create a Offer Class to represent your offer type, and post it to a specific REST URI (https://www.googleapis.com/walletobjects/v1/offerClass). An Offer Class contains data common for every user of the offer type, such as the ID and template used to display offer information in the Google Wallet app. For example, you may create separate Offer Classes for 10%, 20% and 30% discounts. Any updates to an Offer Class will be propagated to all Offer Objects that reference the class. Offer Classes are represented by the Offerclass resource in the Wallet Objects REST API.

An Offer Object contains data specific to a user such as a specific offer ID. Offer Objects are represented by the Offerobject resource in the Wallet Objects REST API. Any updates to Offer Class or Offer Object will be pushed to the affected users' Google Wallet app.

Templates identify how elements found in an Offer Class and Offer Object are displayed in the Google Wallet app. The tutorial provides detailed information about Offer templates.

Refer to the Glossary for a complete list of Wallet Objects terms.

Detailed API flow diagram

The communication flow of the three primary offers API flows are illustrated in the following image:

Detailed API flows.

This diagram identifies the API (REST API, Save to Google API) used for different use case or API flow. The text in bold represents each API flow. All actions between "Your Server" and "Google Wallet" are your responsibility to implement to integrate each flow.

  1. User clicks Save to Google button on offer issuer's website:
    1. Consumer navigates to merchant web site.
    2. Browser issues GET request for your page HTML. Your server generates the Save to Google JWT and g:savetowallet button and adds them to response HTML.
    3. Browser receives and parses HTML and starts loading Save to Google JavaScript.
    4. JavaScript loads, automatically parses the DOM, and displays Save to Google button.
    5. User clicks Save to Google button on merchant's website to save the Offer Object to Google Wallet.
    6. Offer Object is pushed to the Google Wallet app.
  2. This tutorial and the Save To Wallet Tutorial combine to show you how to implement this flow.

  3. User redeems offer:
    1. User scans offer at merchant location.
    2. Offer issuer updates offer as COMPLETED.

    This flow takes place between the Google Wallet app and Google Wallet. You do not implement anything for this flow.

  4. User receives notification that he has an offer for a nearby merchant:
    1. Google compares user's current location to a list of merchant locations (either based on their Google Places ID or within the locations field (lat/longs) of the offer class or object).
    2. If the user has an offer from a merchant within 150 meters of the user's current location, google sends the user the notification "Saved offer nearby."

    The Geolocation Tutorial shows you how to add geolocation to your Wallet Objects.

  5. User deletes offer:
    1. User finds offer.
    2. User deletes offer.
  6. This flow takes place between the Google Wallet app and Google Wallet. You do not implement anything for this flow.

The first flow is the only one that you will need to implement to integrate with the the offer Wallet Objects type. Google also recommends implementing the third flow (populating locations information) so your users can receive offer notifications.

Configure Wallet Objects authentication

The Wallet Objects Save to Wallet and REST APIs use OAuth 2.0 service accounts for authentication to Google Wallet. The requesting application has to prove its own identity to gain access to an API. However, an end-user doesn't have to be prompted during authentication because the requests don't involve user data.

1. Register your application

All applications that access a Google API must be registered through the Developers Console. The result of this registration process is a set of values that are known only to Google and your application (client ID, email address, private key). To register your application:

  1. Access the Developers Console.
  2. Click Create Project. The New project page appears.
  3. Enter a project name and unique ID.
  4. Click Create. The project's Overview page is displayed.
  5. Click APIs & auth in the left-hand menu.
  6. Click APIs in the left-hand menu. A list of all APIs is displayed.
  7. Scroll down and click on the OFF button next to the Wallet Objects API. The API is toggled to "ON."

  8. Note: You must sign up for Wallet Objects API access for the Wallet Object API to appear in the list of APIs.

  9. Click Credentials in the left-hand menu. The credentials for your application appear.
  10. Click Create new Client ID. The Create Client ID dialog box appears.
  11. Select Service account.
  12. Click Create Client ID. The New Public/Private Key Generated dialog box appears.
  13. Click Okay, got it. A new Service Account has been added to your list of accounts and a private key is downloaded to your local file system. This is the only copy of this key and are responsible for keeping this key file in a secure location. You will use this key later.
  14. Copy the Service Account email address. You will use this address later.

Warning: The private key must be stored and managed securely for both developer and production environments. Google does not keep a copy of the private key, only the public key.

2. Tie your service account to Google Wallet Objects Account

Your Wallet Objects Account should have been created for you by your Google point of contact. The Wallet Objects Merchant Console is a web site you can use to manage your Wallet Objects Account and all your associated Wallet Objects (view classes and objects, create and test Discoverables, and so on). You will want to tie your service account created in the Register your application section to the Wallet Objects Merchant Console so you can use its features to manage your Wallet Objects. To tie your service account to the Wallet Objects Merchant Console:

  1. Access the Wallet Objects Merchant Console.
  2. Click Account Management on the left-hand navigation. The Account Info page is displayed.

  3. Note: Make a note of your Issuer Id (also called a Merchant ID). You will use this later when making calls to Wallet Objects.

  4. Click Share. The Share settings appear.
  5. Select the email address from the Register Your Application section and paste it in the Invite people field.
  6. Click Share & save. Your service account is tied to your Wallet Objects Account and your Wallet Objects Account is ready to be used to issue REST calls to Google Wallet.

3. Use OAuth 2.0 for your Server to Server application

The Google OAuth 2.0 Authorization Server supports server-to-server interactions such as those between a web application and Google Cloud Storage. The requesting application has to prove its own identity to gain access to an API, and an end-user doesn't have to be involved.

You need to obtain an access token to authorize your API requests (creating a JSON Web Token (JWT), signing the JWT, forming the access token request, and handling the response). We strongly recommend that you use a client library to simplify this process. Refer to Samples, Tools, and Libraries for a list of client libraries available for different programming languages.

Read Use a library to use the credentials form, encode and sign the JWT claim set if you are using a library for your OAuth 2.0 authentication.

Read Obtain an access token manually if you are manually building all of your OAuth 2.0 authentication code.

Use a library to create a service account credential

The following tabs show code examples in different languages to create a service account credential:

Java

GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
  .setJsonFactory(jsonFactory)
  .setServiceAccountId("ServiceAccountEmail@developer.gserviceaccount.com")
  .setServiceAccountScopes("https://www.googleapis.com/auth/wallet_object.issuer")
  .setServiceAccountPrivateKeyFromP12File(new File("/example/path/to/yourp12file.p12"))
  .build();

Ruby

client = Google::APIClient.new(
  :application_name => 'Wallet Objects App'
)
key = Google::APIClient::KeyUtils.load_from_pkcs12('/example/path/to/yourp12file.p12', 'notasecret')
client.authorization = Signet::OAuth2::Client.new(
  :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
  :audience => 'https://accounts.google.com/o/oauth2/token',
  :scope => 'https://www.googleapis.com/auth/wallet_object.issuer',
  :issuer => 'ServiceAccountEmail@developer.gserviceaccount.com',
  :signing_key => key)
## Request a token for our service account
client.authorization.fetch_access_token!

PHP

$client = new Google_Client();
$client->setApplicationName('Wallet Objects App');
$client->setScopes(array('https://www.googleapis.com/auth/wallet_object.issuer'));
$key = file_get_contents('/example/path/to/yourp12file.p12');
$cred = new Google_Auth_AssertionCredentials(
    'ServiceAccountEmail@developer.gserviceaccount.com',
    array('https://www.googleapis.com/auth/wallet_object.issuer'),
    $key
);
$client->setAssertionCredentials($cred);

Python

f = file('/example/path/to/yourp12file.p12', 'rb')
key = f.read()
f.close()
credentials = SignedJwtAssertionCredentials(
    'ServiceAccountEmail@developer.gserviceaccount.com',
    key,
    scope='https://www.googleapis.com/auth/wallet_object.issuer')
http = httplib2.Http()
http = credentials.authorize(http)

C#

WobCredentials credentials = new WobCredentials(
  WebConfigurationManager.AppSettings["ServiceAccountEmailAddress"],
  WebConfigurationManager.AppSettings["ServiceAccountPrivateKey"],
  WebConfigurationManager.AppSettings["ApplicationName"],
  WebConfigurationManager.AppSettings["IssuerId"]);

// OAuth - setup certificate based on private key file
X509Certificate2 certificate = new X509Certificate2(
  AppDomain.CurrentDomain.BaseDirectory + credentials.serviceAccountPrivateKey,
  "notasecret",
  X509KeyStorageFlags.Exportable);

// create service account credential
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(credentials.serviceAccountEmailAddress) {
  Scopes = new[] { "https://www.googleapis.com/auth/wallet_object.issuer" }
}.FromCertificate(certificate));

The resulting credential will be used when you are executing REST API commands, such as inserting a class.

Obtain an access token manually

Refer to The Using OAuth 2.0 for Server to Server Applications to manually obtain an access token. In summary, you will need to create a JWT and sign it with the private key, and then construct an access token request in the appropriate format. Your application then sends the token request to the Google OAuth 2.0 Authorization Server and an access token will be returned. Your application can access the API only after receiving the access token. When the access token expires, the application repeats the process.

Note: The iss field in the JWT claim set is the service account email address generated from the Google Developers Console in the Register your application section. The scope field in the JWT claim set is a space-delimited list of the permissions the application requests. The valid scope for production applications is https://www.googleapis.com/auth/wallet_object.issuer

Examine the offer template

Following is an image showing the mapping of Offer Class and Offer Object fields to the offer template that you use in the Google Wallet app. Note:

  • A class preface, such as class.issuerName, indicates that the field is defined in the class
  • An object preface, such as object.barcode.label, indicates that the field is defined in the object
  • A * preface, such as *.messages[0].header, indicates that the field can be specified in either object or class. For example, messages can be included in both a class and object resource to be displayed by all users of the class (all holders of a Loyalty card) or by the user of a specific object (a specific library card holder).
Offers object template mapping
class.issuerName
class.title
class.titleImage
object.barcode.label
object.barcode.type
object.barcode.value
object.barcode.alternateText
class.provider
object.validTimeInterval.end
*.infoModuleData.labelValueRows[0].columns[0].label
*.infoModuleData.labelValueRows[0].columns[0].value
*.infoModuleData.labelValueRows[0].hexBackgroundColor
*.infoModuleData.labelValueRows[0].hexFontColor

*.infoModuleData.labelValueRows[0].columns[1].label
*.infoModuleData.labelValueRows[0].columns[1].value
*.showLastUpdateTime
*.imageModulesData.mainImage
*.textModulesData[0].header
*.textModulesData[0].body
*.textModulesData[1].header
*.textModulesData[1].body
*.linksModuleData.uris[0].description
*.linksModuleData.uris[0].uri
*.linksModuleData.uris[1].description
*.linksModuleData.uris[1].uri
*.linksModuleData.uris[2].description
*.linksModuleData.uris[2].uri

Notes

  • This image includes all fields necessary to fill the 1.offer_expanded template. However, not all of these fields are required to create a valid Wallet Object. Refer to Offerclass: insert and Offerobject: insert for a list of required fields.
  • You can use .homepageUri or .linksModuleData to identify a URI to your home page. A URI placed in the .homepageUri field is placed before .linksModuleData.uris[0].

Save an offer

Merchants create Offer Classes to represent offers. A consumer saves an offer by clicking the Save to Google button on a merchant's website. This action causes Offer Objects to be created and saved to the consumers' Google Wallet.

1. Generate Offer Classes

The first step for saving an offer is to create Offerclass resource for each of your offers. Your classes must go through the Class Approval Process before they can be used publicly.

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).

Offer Classes are posted 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

Following is an example Offer Class resource and code example demonstrating how to generate an Offer Class. Note that all required fields are represented in these examples, but not all optional fields. Refer to the REST API reference for a complete list of all optional fields in this resource.

Resource

{
  "kind": "walletobjects#offerClass",
  "id": "#{issuerId}.#{classId}",
  "issuerName": "Baconrista Coffee",
  "issuerData": {
  "kind": "walletobjects#typedValue"
  },
  "renderSpecs": [{
    "viewName": "g_list",
    "templateFamily": "1.offer_list"
  },{
    "viewName": "g_expanded",
    "templateFamily": "1.offer_expanded"
  }],
  "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"
        }
      }
    }
  ],
}

Java

// Define templates to use
List<RenderSpec> renderSpec = new ArrayList<RenderSpec>();

RenderSpec listRenderSpec = new RenderSpec().setViewName("g_list")
    .setTemplateFamily("1.offer_list");
RenderSpec expandedRenderSpec = new RenderSpec().setViewName("g_expanded")
    .setTemplateFamily("1.offer_expanded");

renderSpec.add(listRenderSpec);
renderSpec.add(expandedRenderSpec);

// 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")))
    .setRenderSpecs(renderSpec).setRedemptionChannel("both")
    .setReviewStatus("underReview")
    .setLinksModuleData(linksModuleData)
    .setImageModulesData(imageModuleData)
    .setTextModulesData(textModulesData)
    .setLocations(locations).setAllowMultipleUsersPerObject(true);

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

Ruby

offer_class = generate_class('2945482443380251551', 'OfferClass')
# Makes an API call to insert a new loyalty or offer class
result = api_client.execute(
  :api_method => walletobjects.offerclass.insert,
  :body_object => offer_class
)
def generate_class(issuerId, classId)
  offer_class = {
    "kind" => "walletobjects#offerClass",
    "id" => "#{issuerId}.#{classId}",
    "issuerName" => "Baconrista Coffee",
    "issuerData" => {
    "kind" => "walletobjects#typedValue"
    },
    "renderSpecs" => [{
      "viewName" => "g_list",
      "templateFamily" => "1.offer_list"
    },{
      "viewName" => "g_expanded",
      "templateFamily" => "1.offer_expanded"
    }],
    "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"
          }
        }
      }
    ],
}
end

PHP

// Used to select which templates to use for rendering in this section.
$renderSpecs = array(
    array('templateFamily' => '1.offer_list',
        'viewName' => 'g_list'),
    array('templateFamily' => '1.offer_expanded',
        'viewName' => 'g_expanded'));
// 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->setRenderSpecs($renderSpecs);
$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'
      },
      'renderSpecs': [{
          'viewName': 'g_list',
          'templateFamily': '1.offer_list'
          },{
          'viewName': 'g_expanded',
          'templateFamily': '1.offer_expanded'
      }],
      '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

C Sharp

// Define rendering templates per view
IList<RenderSpec> renderSpec = new List<RenderSpec>();

RenderSpec listRenderSpec = new RenderSpec() {
  ViewName = "g_list",
  TemplateFamily = "1.offer_list"
};

RenderSpec expandedRenderSpec = new RenderSpec() {
  ViewName = "g_expanded",
  TemplateFamily = "1.offer_expanded"
};

renderSpec.Add(listRenderSpec);
renderSpec.Add(expandedRenderSpec);

// Define the Image Module Data
IList<ImageModuleData> imageModulesData = new List<ImageModuleData>();
ImageModuleData image = new ImageModuleData() {
  MainImage = new Image() {
    SourceUri = new Uri() {
      UriValue = "http://farm4.staticflickr.com/3738/12440799783_3dc3c20606_b.jpg",
      Description = "Coffee beans"
    }
  }
};
imageModulesData.Add(image);

// Define Links Module Data
IList<Uri> uris = new List<Uri>();
Uri uri1 = new Uri() {
  Description = "Nearby Locations",
  UriValue = "http://maps.google.com/?q=google"
};
Uri uri2 = new Uri() {
  Description = "Call Customer Service",
  UriValue = "tel:6505555555"
};
uris.Add(uri1);
uris.Add(uri2);

LinksModuleData linksModuleData = new LinksModuleData() {
  Uris = uris
};

// Define Text Module Data
IList<TextModuleData> textModulesData = new List<TextModuleData>();
TextModuleData details = new TextModuleData() {
  Header = "Details",
  Body = "20% off one cup of coffee at all Baconrista Coffee locations.  " +
  "Only one can be used per visit."
};
TextModuleData finePrint = new TextModuleData() {
  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."
};
textModulesData.Add(details);
textModulesData.Add(finePrint);

// Define Geofence locations
IList<LatLongPoint> locations = new List<LatLongPoint>();
locations.Add(new LatLongPoint() { Latitude = 37.422601, Longitude = -122.085286 });
locations.Add(new LatLongPoint() { Latitude = 37.424354, Longitude = -122.09508869999999 });
locations.Add(new LatLongPoint() { Latitude = 40.7406578, Longitude = -74.00208940000002 });

// Create Offer class
OfferClass wobClass = new OfferClass() {
  Id = issuerId + "." + classId,
  IssuerName = "Baconrista Coffee",
  Title = "20% off one cup of coffee",
  Provider = "Baconrista Deals",
  TitleImage = new Image() {
    SourceUri = new Uri() {
      UriValue = "http://farm4.staticflickr.com/3723/11177041115_6e6a3b6f49_o.jpg"
    }
  },
  RenderSpecs = renderSpec,
  RedemptionChannel = "both",
  ReviewStatus = "underReview",
  Locations = locations,
  AllowMultipleUsersPerObject = true,
  ImageModulesData = imageModulesData,
  TextModulesData = textModulesData,
  LinksModuleData = linksModuleData
};

Note: A class must go through the review process before it can be used publicly.

2. Generate Offer Objects

The second step for saving an offer is to generate a Offerobject when a consumer clicks on the Save to Google button on a merchant's website. Following is an example Offer Object resource and code example demonstrating how to generate a Offer Object. Note that all required fields are represented in these examples, but not all optional fields. Refer to the REST API reference for a complete list of all optional fields in this resource.

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",
    "label": "Offer Code",
    "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").setLabel("User Id");

// 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");

Ruby

offer_object = {
 "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",
    "label" => "Offer Code",
    "alternateText" => "12345"
  },
  "validTimeInterval" => {
    "kind" => "walletobjects#timeInterval",
    "start" => "2013-06-12T23:20:50.52Z",
    "end" => "2013-12-12T23:20:50.52Z"
 }
}

PHP

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

$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',
          'label': 'Offer Code',
          'alternateText': '12345'
      }
  }

C Sharp

Barcode barcode = new Barcode() {
  Type = "upcA",
  Value = "123456789012",
  AlternateText = "12345",
  Label = "Offer Code"
};

// Define Wallet Object
OfferObject offerObj = new OfferObject() {
  ClassId = issuerId + "." + classId,
  Id = issuerId + "." + objectId,
  Version = "1",
  Barcode = barcode,
  State = "active"
};

3. Create Save to Google server-side JWT and HTML

The third step for saving an offer is to create a server-side JSON Web Token (JWT) encapsulating the object and then to create the HTML functionality for the Save to Google button. Refer to the Save to Google Tutorial to continue with this part of your integration.

Note: You will need the private key and email address obtained in the Authentication section for the Save to Google API.

Update an offer

You can update Offer Class fields if there is a change affecting all consumers holding the offer. You can also update a specific Offer Object if there are changes to a specific consumer's offer. Updating an offer is a 3-step process: Use a GET request to retrieve the Offer Class or Offer Object, make the change to the class or object resource, use an UPDATE request to update the class or object.

Update a specific Offer Class

The following REST URIs are used to get a class and to put (update) a class:

GET https://www.googleapis.com/walletobjects/v1/offerClass/resourceId
PUT https://www.googleapis.com/walletobjects/v1/offerClass/resourceId

The following tabs provide code examples in different languages to update a specific class:

Java

// Get the specific Offer Class
OfferClass cls = client.offerclass().get("1234567.ExampleOfferClass").execute();
// Update the title
cls.setTitle("Title Updated");
cls.setReviewStatus("underReview);
// Update the Offer Class
OfferClass returnCls = client.offerclass().update(cls.getClassId(), cls).execute();

Ruby

# Get the specific Offer Class
result = api_client.execute(
   :api_method => walletobjects.offerclass.get,
   :parameters => {'resourceId' => '1234567.ExampleOfferClass' }
)
 # Update the title field
 class_toupdate["title"] = "Title Updated"
 # need to update reviewStatus for the class to be reviewed again
 class_toupdate["reviewStatus"] = "underReview"
 # Update the Offer Class
 update_result = api_client.execute(
   :api_method => walletobjects.offerclass.update,
   :parameters => {'resourceId' => '1234567.ExampleOfferClass' },
   :body_object => class_toupdate
 )

PHP

// Get the specific Offer Class
Google_OfferClass offerClass = $service->offerclass->get('1234567.ExampleOfferClass');
// Update the title
offerClass->setTitle("Title Updated");
offerClass->setReviewStatus("underReview);
// Update the Offer Class
Google_OfferClass offerClass = $service->offerclass->update('1234567.ExampleOfferClass',offerClass);

Python

# Get the specific Offer Class
offer_class = service.offerclass().get(resourceId='1234567.ExampleOfferClass')
# Update the title
offer_class.[title] = 'Title Updated'
offer_class.['reviewStatus'] = 'underReview'
# Update the Offer Class
api_request = service.offerclass().update(resourceId='1234567.ExampleOfferClass',body=offer_class)
api_response = api_request.execute()

A get request is made to retrieve a specific class based on classID. Google Wallet responds to the request with the Offer Class. The air craft type changed to "Title Updated." Finally, an update is made to update the class in Google Wallet.

Note: Changes to any of the fields listed in the Trigging an automatic review portion of the Class Approval Process will result in the class being re-reviewed.

Update a specific Offer Object

The following REST URIs are used to get an object and to put (update) an object:

GET https://www.googleapis.com/walletobjects/v1/offerObject/resourceId
PUT https://www.googleapis.com/walletobjects/v1/offerObject/resourceId

The following tabs provide code examples in different languages to update an object:

Java

// Get the specific Offer Object
OfferObject obj = client.offerobject().get("1234567.ExampleOfferObject").execute();
// Update the version and barcode
obj.setVersion(obj.getVersion() + 1L);
obj.setBarcode(barcode);
// Update the Offer Object
OfferObject returnObj = client.offerobject().update(obj.getId(), obj).execute();

Ruby

# Get the specific Offer Object
result = api_client.execute(
   :api_method => walletobjects.loyaltyobject.get,
   :parameters => {'resourceId' => '1234567.ExampleOfferObject' }
)
# Update the version field
obj_toupdate["version"] = obj_toupdate["version"].to_i + 1
obj_toupdate["barcode"] = barcode
# Update the Offer Object
update_result = api_client.execute(
   :api_method => walletobjects.offerobject.update,
   :parameters => {'resourceId' =>  '1234567.ExampleOfferObject'},
   :body_object => obj_toupdate
 )

PHP

// Get the specific Offer Object
Google_OfferObject offerObject = $service->offerobject->get('1234567.ExampleOfferObject');
// Update the version and barcode
offerObject->setVersion(offerObject.getVersion() + 1);
offerObject->setBarcode(barcode);
// Update the Offer Object
Google_OfferObject offerObject = $service->offerobject->update('1234567.ExampleOfferObject',offerObject);

Python

# Get the specific Offer Object
offer_object = service.offerobject().get(resourceId='1234567.ExampleOfferObject')
# Update the version and barcode
offer_object['version'] = str(int(offer_object['version']) + 1)
offer_object.['barcode'] = barcode
# Update the Offer Object
api_request = service.offerobject().update('1234567.ExampleOfferObject',body=offer_object)
api_response = api_request.execute()

A get request is made to retrieve a specific object based on objectID. Google Wallet responds to the request with the Offer Object. The version of the object is incremented and the barcode is changed. Finally, an update is made to update the object in Google Wallet.

Other REST API use cases

The following section demonstrates how to use the REST API for other use cases.

Get a specific Offer Class

The following REST URI is used to get a Offer Class with a specific Class ID (resourceId):

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

The following tabs provide code examples in different languages to get a Offer Class with a specific Offer ID (resourceId).

Java

// Get the specific Offer Class
OfferClass cls = client.offerclass().get("1234567.ExampleOfferClass").execute();

Ruby

# Get the specific Offer Class
result = api_client.execute(
   :api_method => walletobjects.offerclass.get,
   :parameters => {'resourceId' => '1234567.ExampleOfferClass' }
)

PHP

// Get the specific Offer Class
Google_OfferClass offerClass = $service->offerclass->get('1234567.ExampleOfferClass');

Python

# Get the specific Offer Class
offer_class = service.offerclass().get(resourceId='1234567.ExampleOfferClass')

Get a specific Offer Object

The following REST URI is used to get a Offer Object with a specific object ID (resourceId):

GET https://www.googleapis.com/walletobjects/v1/offerObject/resourceId

The following tabs provide code examples in different languages to get a Offer Object with a specific object ID (resourceId).

Java

// Get the specific Offer Object
OfferObject obj = client.offerobject().get("1234567.ExampleOfferClass").execute();

Ruby

# Get the specific Offer Object
result = api_client.execute(
    :api_method => walletobjects.loyaltyobject.get,
    :parameters => {'resourceId' => '1234567.ExampleOfferObject' }
 )

PHP

// Get the specific Offer Object
Google_OfferObject offerObject = $service->offerobject->get('1234567.ExampleOfferObject');

Python

# Get the specific Offer Object
offer_object = service.offerobject().get(resourceId='1234567.ExampleOfferObject')

List Offer Classes

The following REST URI is used to list (GET) Offer Classes:

GET https://www.googleapis.com/walletobjects/v1/offerClass?issuerId=issuerId

The following tabs provide code examples in different languages to list Offer Classes created by an issuer with a specific issuer ID (issuerId). Note that Offer Class1 and OfferClass2 represent Offer Classes. All results are returned if maxResults isn't defined.

Java

OfferClassListResponse classes = client.offerclass()
  .list(1234567L)
  .setMaxResults(25)
  .execute();

Ruby

result = api_client.execute(
  :api_method => walletobjects.offerclass.list,
  :parameters => {'issuerId' => '1234567',
    'maxResults' => '25' }
)

PHP

$optParams = array('maxResults' => '25');
$offerClasses = $service->offerclass->listOfferclass('1234567', $optParams);

Python

api_request = service.offerclass().list(issuerId='1234567', maxResults='25')
offer_classes = api_request.execute()

Google Wallet responds to the request with a list of Offers Classes:

{
  "pagination": {
    "kind": "walletobjects#pagination",
    "resultsPerPage": integer,
    "nextPageToken": string
  },
  "resources": [
    {OfferClass1},
    {OfferClass2}
  ]
}

List Offer Objects

The following REST URI is used to list (GET) Offer Objects:

GET https://www.googleapis.com/walletobjects/v1/offerObject?classId=classId

The following tabs provide code examples in different languages to list Offer Objects of a specific Offer Class ID (classId). Note that OfferObject1 and OfferObject2 represent Offer Objects. All results are returned if maxResults isn't defined.

Java

OfferObjectListResponse objs = client.offerobject()
  .list("1234567.ExampleOfferClass")
  .setMaxResults(25)
  .execute();

Ruby

result = api_client.execute(
  :api_method => walletobjects.offerobject.list,
  :parameters => {'classId' => '1234567.ExampleOfferClass',
    'maxResults' => '25' }
)

PHP

$optParams = array('maxResults' => '25');
$offerObjects = $service->offerobject->list('1234567.ExampleOfferClass', $optParams);

Python

api_request = service.offerobject().list(classId='1234567.ExampleOfferClass',
  maxResults='25')
obj_list = api_request.execute()

Google Wallet responds to the request with a list of Offers Objects:

    {
      "pagination": {
        "kind": "walletobjects#pagination",
        "resultsPerPage": integer,
        "nextPageToken": string
      },
      "resources": [
        {OfferObject1},
        {OfferObject2}
      ]
    }

Next steps

Once you have a working integration in your development environment, make sure it meets Google's acceptance criteria before going live to consumers. See Offers Merchant Acceptance Testing for more information.