Build consumable digital transactions

This guide explains how to add digital transactions to your Conversational Action, so users can purchase your consumable digital goods.

Key terms: A consumable digital good is a stock-keeping unit (SKU) that a user can use and purchase more than once, like a quantity of in-game currency for an Android game. This digital good is different from a non-consumable digital good which a user can only purchase once.

For more information on consumable one-time products, refer to the Android documentation on one-time product-specific features.

Transaction flow

This guide outlines each development step as they occur in a digital goods transaction flow. When your Action handles transactions for digital goods, it uses the following flow:

  1. Set up a digital purchases API client: Your Action uses the digital purchases API to communicate with your Google Play inventory and transact. Before your Action does anything else, it creates a JWT client with a service key to communicate with the digital purchases API.
  2. Gather information: Your Action gathers basic information about the user and your Google Play inventory to prepare for a transaction.
    1. Validate transaction requirements: Your Action uses the digital transactions requirements helper at the start of the purchase flow to make sure the user can transact.
    2. Gather available inventory: Your Action checks your Google Play inventory and identifies what items are currently available to purchase.
  3. Build the order: Your Action presents the available digital goods to the user so they can select one to purchase.
  4. Complete the purchase: Your Action uses the digital purchases API to initiate a purchase with the user’s selection from the Google Play store.
  5. Handle the result: Your Action receives a status code for the transaction and notifies the user that the purchase was successful (or takes additional steps).
  6. Make the purchase repeatable: Your Action uses the digital purchases API to "consume" the purchased item, making that item available for purchase again by that user.

Restrictions and review guidelines

Additional policies apply to Actions with transactions. It can take us a few weeks to review Actions that include transactions, so factor that time in when planning your release schedule. To ease the review process, make sure you comply with the policies and guidelines for transactions before submitting your Action for review.

Actions that sell digital goods can only be deployed in the following countries:

  • Australia
  • Brazil
  • Canada
  • Denmark
  • France
  • Indonesia
  • Italy
  • Japan
  • Mexico
  • Netherlands
  • Norway
  • Poland
  • Russia
  • Singapore
  • Spain
  • Sweden
  • Thailand
  • Turkey
  • United Kingdom
  • United States

Prerequisites

Before you incorporate digital transactions into your Action, you need the following prerequisites:

  • A developer account and a merchant account on Google Play, to manage your digital goods in the Google Play console.

  • A web domain that’s verified in the Google Search Console. This domain doesn’t need to be associated with a publicly launched web site, we just need to reference your web domain.

  • An Android app with the com.android.vending.BILLING permission in the Google Play console. Your digital goods will be “in-app purchases” associated with this app in the Google Play console.

    You also need to create a release in the Play console with this app, but if you don’t want the release to be public you can create a closed alpha release.

    If you don’t have an Android app already, follow the Associate an Android App instructions.

  • One or more managed products in the Google Play console, which are the digital goods that you sell with your Action. Note that you can't create managed products in the Play console until you set up the Android app prerequisite.

    If you don’t have managed products already, follow the Create your Digital Goods instructions.

Associate an Android App

If you don’t currently have an Android app with the billing permission in the Google Play console, follow these steps:

  1. In Android Studio or the Android IDE of your choice, create a new project. Choose options in the project setup prompts to create a very basic app.
  2. Give the project a package name, like com.mycompany.myapp. Don't leave this name as default, since you can't upload packages that include com.example to the Play console.
  3. Open your app's AndroidManifest.xml file.
  4. Add the following line of code inside the manifest element:

    <uses-permission android:name="com.android.vending.BILLING" />

    Your AndroidManifest.xml file should look like the following code block:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.mycompany.myapp">
        <uses-permission android:name="com.android.vending.BILLING" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme" />
    </manifest>
    
  5. Build your app as a signed APK. In Android Studio, follow these steps:

    1. Go to Build, Generate Signed Bundle / APK.
    2. Click Next.
    3. Under Key store path, click Create new.
    4. Fill each field then click OK. Take note of your Key store password and Key password, and store these in a safe place, since you'll use these later.
    5. Click Next.
    6. Select release.
    7. Select V1 (JAR Signature).
    8. Click Finish.
    9. After a few seconds, Android Studio generates an app-release.apk file. Locate this file for later use.
  6. In the Google Play console, create a new application.

  7. Go to App releases.

  8. Under Closed tracks, go to Manage then Alpha.

  9. Click the Create Release button.

  10. Under Let Google manage and protect your signing key, enter your signing key information.

  11. Upload your APK file.

  12. Click Save.

Create your Digital Goods

If you don’t currently have any digital goods in the Play console, follow these steps:

  1. In the Google Play console, go to In-app products then Managed products. If you see a warning, follow the previous instructions to create an Android app or click the link to create a merchant profile.
  2. Click Create Managed product.
  3. Fill out the fields for your digital product. Take note of the Product ID, which is how you’ll reference this product from your Action.
  4. Click Save.
  5. Repeat steps 2-4 for each product you want to sell.

Example consumable goods in the Google Play console.

Prepare your Actions project

With your digital goods set up in the Google Play console, you must enable digital transactions and associate your Actions project with your Play app.

Setup

To turn on digital goods transactions in your Actions project, follow these steps:

  1. In the Actions console, open your project or create a new one.
  2. Go to Deploy then Directory information.
  3. Under Additional information and Transactions, check the Yes box under Do your Actions use the Digital Purchase API to perform transactions of digital goods.
  4. Click Save.

Create a digital goods API key

To send requests to the digital goods API, you need to download a JSON service account key associated with your Actions console project.

To retrieve your service account key, follow these steps:

  1. In the Actions console, click the three dots icon in the upper-right corner, then Project settings.
  2. Find your Action’s Project ID.
  3. Follow this link, replacing "<project_id>" with your project's ID: https://console.developers.google.com/apis/credentials?project=project_id
  4. In the main navigation, go to Credentials.
  5. On the page that appears, click Create credentials, then Service account key.
  6. Go to Service Account, and click New Service Account.
  7. Give the service account a name like digitaltransactions.
  8. Click Create.
  9. Set the Role to Project > Owner.
  10. Click Continue.
  11. Click Create Key.
  12. Select the JSON key type.
  13. Click Create key and download the JSON service account key.

Save this service account key in a safe place. You’ll use this key in your fulfillment to create a client for the digital purchases API.

Connect to your Play inventory

In order to access your digital goods from an Actions project, associate your web domain and app with your project as connected properties.

To connect your Play console web domain and app to your Actions project, follow these steps:

  1. In the Actions console, go to Deploy then Brand verification.
  2. If you haven't connected any properties, first connect a website:

    1. Click the web property (</>) button.
    2. Enter the URL for your web domain and click Connect.

    Google sends an email with further instructions to the individual who’s verified for that web domain in the Google Search Console. Once the recipient of this email follows those steps, the website should appear under Brand verification.

  3. Once you have at least one connected website, perform the following steps to connect your Android app:

    1. In the Actions console, go to Deploy then Brand verification.
    2. Click Connect App.
    3. On the page that appears, follow the instructions to verify your web domain on the Play console. Select the Play app that contains your digital goods, and enter the web domain URL exactly as it’s shown on the Brand verification page.

      Once again, Google sends a verification email to the verified owner of the domain. Once they approve the verification, your Play app should appear under Brand verification.

    4. Enable Access Play purchases.

Image showing website and apps connected to the Actions project.

Build your purchase flow

With your Actions project and digital goods inventory prepared, build a digital goods purchase flow in your conversation fulfillment webhook.

1. Set up a digital purchases API client

In your conversation fulfillment webhook, create a JWT client with your service account JSON key and the https://www.googleapis.com/auth/actions.purchases.digital scope.

The following Node.js code creates a JWT client for the digital purchases API:

  const serviceAccount = {'my-file.json'};
  const request = require('request');
  const {google} = require('googleapis');

  const jwtClient = new google.auth.JWT(
    serviceAccount.client_email, null, serviceAccount.private_key,
    ['https://www.googleapis.com/auth/actions.purchases.digital'],
    null
  );

2. Gather information

Before the user can make a purchase, your Action gathers information about the user’s ability to make purchases and what goods are available from your inventory.

2. a. Validate digital purchase requirements

It’s a good practice to make sure the user’s account is set up to perform transactions before giving them the option to make a purchase. This step includes checking that the user has a payment method configured and they’re in a locale where digital transactions are supported. To do so, you should transition to a scene that performs a digital purchase check.

To create a digital purchase check scene, follow these steps:

  1. From the Scenes tab, add a new Scene with the name DigitalPurchaseCheck.
  2. Under Slot filling, click + to add a new slot.
  3. Under Select type, select actions.type.DigitalPurchaseCheckResult as the slot type.
  4. In the slot name field, give the slot the name DigitalPurchaseCheck.
  5. Enable the Customize slot value writeback checkbox (enabled by default).
  6. Click Save.

A digital purchase check will result in one of the following outcomes:

  • If the requirements are met, the session parameter is set with a success condition and you can proceed with allowing the user to purchase digital goods.
  • If one or more of the requirements cannot be met, the session parameter is set with a failure condition. In this case, you should pivot the conversation away from the transactional experience, or end the conversation.

To handle a Digital Purchase check result, follow these steps:

  1. From the Scenes tab, select your newly created DigitalPurchaseCheck scene.
  2. Under Condition, click + to add a new condition.
  3. In the text field, enter the following condition syntax to check for the success condition:

    scene.slots.status == "FINAL" && session.params.DigitalPurchaseCheck.resultType == "CAN_PURCHASE"
    
  4. Hover your cursor over the condition you just added and click the up arrow to place it before if scene.slots.status == "FINAL".

  5. Enable Send prompts and provide a simple prompt letting the user know they are ready to make a transaction:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                You are ready to purchase digital goods.
    
  6. Under Transition select another scene, allowing the user to continue the conversation and proceed with making a transaction.

  7. Select the condition else if scene.slots.status == "FINAL".

  8. Enable Send prompts and provide a simple prompt letting the user know they are unable to make a transaction:

    candidates:
      - first_simple:
          variants:
            - speech: Sorry you cannot perform a digital purchase.
    
  9. Under Transition, select End conversation to end the conversation.

2. b. Gather available inventory

Use the digital purchases API to request your currently available Play store inventory, then build that into an array of JSON objects for each product. You reference this array later to show the user what options are available for purchase.

Each of your digital goods is represented as a SKU in a JSON format. The following Node.js code outlines the expected formatting of each SKU:

body = {
  skus: [
    skuId: {
      skuType: one of "APP" or "UNSPECIFIED"
      id: string,
      packageName: string
    }
    formattedPrice: string,
    title: string,
    description: string
  ]
}

Send a POST request to the https://actions.googleapis.com/v3/packages/{packageName}/skus:batchGet endpoint, where {packageName} is your app’s package name in the Google Play Console (for example, com.myapp.digitalgoods), and format the result into an array of SKU objects.

To only retrieve specific digital goods in the resulting array, list the product IDs for digital goods (as shown under each in-app product in the Google Play Console) that you want to make available for purchase in body.ids.

The following Node.js code requests a list of available goods from the digital purchases API and formats the result as an array of SKUs:

return jwtClient.authorize((err, tokens) => {
    if (err) {
      throw new Error(`Auth error: ${err}`);
    }

    const packageName = 'com.example.projectname';

    request.post(`https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`, {
      'auth': {
        'bearer': tokens.access_token,
      },
      'json': true,
      'body': {
        'conversationId': conv.session.id,
        'skuType': 'APP',
        // This request is filtered to only retrieve SKUs for the following product IDs
        'ids': ['consumable.1']
      },
    }, (err, httpResponse, body) => {
      if (err) {
        throw new Error(`API request error: ${err}`);
      }
      console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
      console.log(JSON.stringify(body));
    });
  });
});

3. Build the order

To start the user's digital purchase, present a list of your digital goods available for purchase. You can use a variety of rich response types to represent your stock and prompt the user to make a selection.

Tip: If you only have one item to sell, simplify the interaction with a simple response and handle yes or no as a follow-up.

The following Node.js code reads an inventory array of SKU objects and creates a list response with one list item for each:

const items = [];
const entries = [];
skus.forEach((sku) => {
   const key = `${sku.skuId.skuType},${sku.skuId.id}`
   items.push({
       key: key
   });
   entries.push({
       name: key,
       synonyms: [],
       display: {
           title: sku.title,
           description: `${sku.description} | ${sku.formattedPrice}`,
       }
   });
});

conv.session.typeOverrides = [{
   name: 'type_name',
   mode: 'TYPE_REPLACE',
   synonym: {
       entries: entries
   }
}];

conv.add(new List({
   title: 'List title',
   subtitle: 'List subtitle',
   items: items,
}));

Create purchase from user selection

Once the user selects an item you can create the order. To do so, upon the slot associated with the selected item, you can call out to your webhook to create the order. From your fulfillment, save the order data to a session parameter. The order object is used across scenes for the same session.

conv.session.params.purchase = {
  "@type": "type.googleapis.com/google.actions.transactions.v3.CompletePurchaseValueSpec",
  "skuId": {
    "skuType": "<SKU_TYPE_IN_APP>",
    "id": "<SKU_ID>",
    "packageName": "<PACKAGE_NAME>"
  },
  "developerPayload": ""
};

In Actions Builder, you can instead use the JSON editor to configure the slot with the above order object. Both implementations use the same format for CompletePurchaseValueSpec, which you can find in the JSON webhook payload reference.

4. Complete the purchase

Once the user selects an item you can complete the purchase. Once you fill the slot associated with the selected item, you should transition to a scene that performs a complete purchase.

Create Complete Purchase Scene

  1. From the Scenes tab, add a new scene with the name CompletePurchase.
  2. Under Slot filling, click + to add a new slot.
  3. Under Select type, select actions.type.CompletePurchaseValue as the slot type.
  4. In the slot name field, give the slot the name CompletePurchase.
  5. Enable the Customize slot value writeback checkbox (enabled by default).
  6. Under Configure slot, select Use session parameter from the dropdown.
  7. Under Configure slot, enter the name of the session parameter used to store the order into the text field (i.e. $session.params.purchase).
  8. Click Save.

5. Handle the result

A slot with the actions.type.CompletePurchaseValue type can have the following results:

  • PURCHASE_STATUS_OK: The purchase was successful. The transaction is complete at this point, so exit the transactional flow and pivot back to your conversation.
  • PURCHASE_STATUS_ALREADY_OWNED: The transaction failed because the user already owns that item. Avoid this error by checking the user's previous purchases and tailoring the items shown so they don't have the option to re-purchase items they already own.
  • PURCHASE_STATUS_ITEM_UNAVAILABLE: The transaction failed because the requested item is not available. Avoid this error by checking the available SKUs closer to the time of purchase.
  • PURCHASE_STATUS_ITEM_CHANGE_REQUESTED: The transaction failed because the user decided to purchase something else. Reprompt with your order building so the user can make another decision right away.
  • PURCHASE_STATUS_USER_CANCELLED: The transaction failed because the user cancelled the purchase flow. Since the user prematurely exited the flow, ask the user if they want to retry the transaction or exit the transaction altogether.
  • PURCHASE_STATUS_ERROR: The transaction failed for an unknown reason. Let the user know the transaction failed, and ask if they want to try again.
  • PURCHASE_STATUS_UNSPECIFIED: The transaction failed for an unknown reason, resulting in an unknown status. Handle this error status by letting the user know the transaction failed, and ask if they want to try again.

You should handle each of these results from your CompletePurchase scene.

  1. From the Scenes tab, select your newly created CompletePurchase scene.
  2. Under Condition, click + to add a new condition.
  3. In the text field, enter the following condition syntax to check for the success condition:

    scene.slots.status == "FINAL" && session.params.CompletePurchase.resultType == "PURCHASE_STATUS_OK"
    
  4. Hover your cursor over the condition you just added and click the up arrow to place it before if scene.slots.status == "FINAL".

  5. Enable Send prompts and provide a simple prompt letting the user know they are ready to make a transaction:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Your purchase was successful.
    
  6. Under Transition select End conversation to end the conversation.

Repeat the above steps for each type of purchase result you wish to support.

6. Make the purchase repeatable

After a successful transaction, send a POST request to the digital purchases API to consume the item, allowing the user to purchase it again. Send your request to the https://actions.googleapis.com/v3/conversations/{sessionId}/entitlement:consume endpoint with the ID of the session found in session.id.

Your POST request must also include the purchase token object associated with the user’s purchase, found in the user’s request JSON under packageEntitlements.entitlements.inAppDetails.inAppPurchaseData.purchaseToken.

The following code sends a consume request to the digital purchases API and reports whether that request was successful:

request.post(`https://actions.googleapis.com/v3/conversations/${conv.session.id}/entitlement:consume`, {
  'auth': {
    'bearer': tokens.access_token,
  },
  'json': true,
  'body': {
  // This purchase token is in both the purchase event and the user's entitlements
  // in their request JSON
    "purchaseToken": entitlement.purchaseToken
  },
  }, (err, httpResponse, body) => {
    if (err) {
     throw new Error(`API request error: ${err}`);
    }
  console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
  console.log(JSON.stringify(httpResponse));
  console.log(JSON.stringify(body));
  resolve(body);
});

// Make sure the consume request was successful. In production, don't notify the user; handle failures on the back end
return consumePromise.then(body => {
  const consumed = Object.keys(body).length === 0;
  if (consumed) {
    conv.add(`You successfully consumed ${id}`);
  } else {
    conv.add(`Failed to consume: ${id}`);
  }
});

Reflect the user’s purchases

When a user queries your Action, the request JSON's user object includes a list of their purchases. Check this information and change your Action’s response based on what content the user has paid for.

The following sample code shows a request’s user object that includes packageEntitlements of previous in-app purchases they made for the com.digitalgoods.application package:

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "example_session_id",
    "params": {},
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
      "packageEntitlements": [
        {
          "packageName": "com.digitalgoods.application",
          "entitlements": [
            {
              "sku": "non-consumable.1",
              "skuType": "APP"
            }
            {
              "sku": "consumable.2",
              "skuType": "APP"
            }
          ]
        },
        {
          "packageName": "com.digitalgoods.application",
          "entitlements": [
            {
              "sku": "annual.subscription",
              "skuType": "SUBSCRIPTION",
              "inAppDetails": {
                "inAppPurchaseData": {
                  "autoRenewing": true,
                  "purchaseState": 0,
                  "productId": "annual.subscription",
                  "purchaseToken": "12345",
                  "developerPayload": "HSUSER_IW82",
                  "packageName": "com.digitalgoods.application",
                  "orderId": "GPA.233.2.32.3300783",
                  "purchaseTime": 1517385876421
                },
                "inAppDataSignature": "V+Q=="
              }
            }
          ]
        }
      ]
     }
   },
  "homeStructure": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}