Build Digital Transactions

This guide walks you through the process of developing an Actions project that incorporates transactions for your digital goods managed through the Google Play store.

Types of digital goods

Your Actions can perform transactions for the following types of digital goods in the Google Play store:

  • One-time purchases only charge the user once. These include purchases like a paid upgrade to a premium, ad-free app experience or a game expansion.
  • Subscriptions automatically charge the user on a recurring schedule. These include purchases like a monthly fee for premium features.

Transaction flow

When your Actions project facilitates digital transactions, it uses the following flow:

  1. Gathers information. Before your Action can handle a transaction, you'll first need to see what items are currently available for the user to purchase.
    1. Checks for available Play store SKUs. You query the Play store with the ID of one or more packages you own, and it will return a list of product IDs (SKUs) that are available for the user to purchase.
    2. Accesses the user's existing goods (optional). You can also reference the user's existing entitlements to see what items they've already purchased and tailor their ordering experience.
  2. Builds the order. With the list of available SKUs, you present the user an interactive list of purchasable items in the form of a carousel or list rich response.
  3. Completes the purchase. When the user selects an item, you send a purchase request to the Play store containing the SKU of that item and the ID of the user who wishes to purchase it. The user is then prompted to complete the transaction using a pre-defined flow.
  4. Describes the purchase status. After the user completes the transaction flow, you receive a status code. The user sees a response from you that describes whether the transaction was successful or failed.

Review guidelines

Keep in mind that additional policies apply to Actions with transactions. Please be sure to review our policies and guidelines for transactions.

Play store setup

Since digital transactions are for selling goods that you own in the Google Play store, you'll need to have a Google Play Developer account and a payments profile.

To start selling digital goods in the Google Play store, read the Play console and payments center docs for instructions on registering for a Google Play Developer account and creating a payments profile.

Build your project

The following sections explain how to build an Actions project that handles transactions for digital goods.

Setup

Before you can start developing your transactional flow, complete the following setup steps.

Create a project

When creating your Actions project, you must specify that you want to perform transactions.

To get started, you need to perform the following steps in the Actions console:

  1. Click Add/import project to create a new project or use an existing project.
  2. Navigate to Deploy > Directory information.
  3. Under Additional information > Transactions, check the box that says "Do your Actions use the Digital Purchase API to perform transactions of digital goods?".

Set up a service account for the digital purchases API

To send requests to the digital purchases API, you need to download a JSON service account key associated with your Actions console project, then exchange the service account key for a bearer token that can be passed into the Authorization header of the HTTP request.

To retrieve your service account key, perform the following steps:

  1. Follow this link, swapping "example-project-1" for your project ID: https://console.developers.google.com/apis/api/actions.googleapis.com/overview?project=example-project-1
    • You can find your Actions project ID in the Actions console project settings by clicking the gear icon in the left navigation.
    • If you see an Enable button, click it.
  2. Follow this link, swapping "example-project-1" for your project ID: https://console.developers.google.com/apis/credentials?project=example-project-1
  3. Click Create credentials > Service Account Key.
  4. Click the Select box under Service Account and click New Service Account.
  5. Give the Service Account a name, like "digitaltransactions", and the Role of Project Owner.
  6. Select the JSON key type.
  7. Click Create.
  8. A JSON service account key will be downloaded to the local machine.

In your fulfillment, you'll exchange this service key for a bearer token using the Google APIs client library and the https://www.googleapis.com/auth/actions.purchases.digital scope. You can find installation steps and examples on the API client library GitHub page.

Any Android apps whose goods you want users to purchase need to be associated with your Actions project as a connected property.

To connect an app for digital transactions in your Actions project, perform the following steps:

  1. In the Actions console, navigate to Advanced Options > Brand verification.
  2. If you haven't connected any properties, you'll first need to connect a website:
    1. Click the </> button.
    2. Enter the URL for the website you want to connect and click Connect. Google sends you an email confirming the request and an email to the website owner (verified in Search Console) asking them to confirm the association.
  3. Once you have at least one connected website, perform the following steps to connect your Android app:
    1. Click Connect App.
    2. Follow the instructions shown.
    3. Enable Access Play purchases.

1. Gather information

First, your Action gathers information about what items the user can purchase.

1a. Check for available Play store SKUs

User experience

The user expresses an interest in purchasing one of your items in the Play store. For example, your Action asks the user if they'd like to purchase a premium subscription to your app, and they respond "yes".

Create a digital purchases API client

In order to use the digital purchases API, you need to create a JWT client using the https://www.googleapis.com/auth/actions.purchases.digital scope and the service key you generated earlier:

  const serviceAccount = { /* your service account info (load from env) */};
  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
  );
Request available SKUs

Your Action needs to query the Play store to find out what items are currently available for purchase. You'll provide one or more Play store IDs for apps that you want to query.

You can request the list of available SKUs by sending a POST request to the digital purchases API:

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,
      // use the ids that you want to pull info from here.
      'body': {
        'conversationId': conversationId,
        'skuType': 'SKU_TYPE_IN_APP',
        'ids': ['consumable.0', 'consumable.1', 'subscription.monthly.0']
      },
    }, (err, httpResponse, body) => {
      if (err) {
        throw new Error(`API request error: ${err}`);
      }
      console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
      console.log(JSON.stringify(body));
    });
  });
});

The response body will contain arrays of SKU objects. Each SKU object has attributes that you'll use to present the item and submit the purchase, as shown in the following code snippet:

body = {
  skus: [
    skuId: {
      skuType: one of "APP", "SUBSCRIPTION", or "UNSPECIFIED"
      id: string,
      packageName: string
    }
    formattedPrice: string,
    title: string,
    description:string,
    subscriptionDetails: {
      subscriptionPeriod: string,
      freeTrialPeriod: string,
      formattedIntroductoryPrice: string
      introductoryPrice: Money
      introductoryPricePeriod: string
    }
  ]
}

1b. Reference the user's existing goods (optional)

When a user sends a request to your webhook, that request's JSON will include a list of purchases they've made for any apps you've linked to this project:

{
  "user": {
    "userId": "xxxx",
    "locale": "en-US",
    "lastSeen": "2018-02-09T01:49:23Z",
    "packageEntitlements": [
      {
        "packageName": "com.abcd.edfg.hijk",
        "entitlements": [
          {
            "sku": "com.abcd.edfg.hijk",
            "skuType": "APP"
          }
        ]
      },
      {
        "packageName": "com.abcd.edfg.lmno",
        "entitlements": [
          {
            "sku": "lmno_jun_2017",
            "skuType": "SUBSCRIPTION",
            "inAppDetails": {
              "inAppPurchaseData": {
                "autoRenewing": true,
                "purchaseState": 0,
                "productId": "lmno_jun_2017",
                "purchaseToken": "12345",
                "developerPayload": "HSUSER_IW82",
                "packageName": "com.abcd.edfg.lmno",
                "orderId": "GPA.233.2.32.3300783",
                "purchaseTime": 1517385876421
              },
              "inAppDataSignature": "V+Q=="
            }
          }
        ]
      }
    ]
  },
  "conversation": {
    "conversationId": "1518141160297",
    "type": "NEW"
  },
  "inputs": [
    {
      "intent": "actions.intent.MAIN",
      "rawInputs": [
        {
          "inputType": "VOICE",
          "query": "Talk to My Test App"
        }
      ]
    }
  ],
  ...
}

In your transactional flow, you could use this information to dynamically exclude items that the user has already purchased, or end the transaction altogether if they've already purchased every available item.

2. Build the order

With the SKUs available for purchase, your Action prompts the user to select an item.

User experience

The user is presented with a list of items they can select for purchase. On a smart speaker, they are read options and prompted to respond. If they are on a screened device, they can tap one of the items.

Build fulfillment

Use the array of SKUs to create a rich response describing the item(s) available for purchase. The code below uses a list response because its contents are simple and can support up to 30 items.

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

3. Complete the purchase

Your Action uses the digital purchases API to initiate a purchase with the Play store.

User experience

Once the user has selected an item, the purchase process will be initiated with the Play store. If the conversation isn't happening on the user's phone, they'll be prompted to complete the transaction there. The phone then walks the user through the rest of the transaction (selecting a payment method and purchase confirmation).

Build fulfillment

Call the CompletePurchase() function with details about the SKU that was picked. This function call will handle the rest of the purchase flow for you.

Assuming you used a list, the actions.intent.OPTION event for handling the user's item selection will contain an option parameter describing which SKU was picked:

app.intent('actions.intent.OPTION', (conv, params, option) => {
  let [skuType, id] = option.split(',');

  conv.ask(new CompletePurchase({
    skuId: {
      skuType: skuType,
      id: id,
      packageName: <PACKAGE_NAME>,
    },
  }));
});

4. Describe the purchase status

Once the Play store finishes the user's transaction flow, your Action will receive a status code and notify the user accordingly.

User experience

After the user confirms the purchase on their phone, they receive a response from your Action that lets them know the purchase was successful. Then, your Action ends the conversation.

Fulfillment

When a user completes their purchase, the actions.intent.COMPLETE_PURCHASE Actions SDK intent is triggered. In Dialogflow, this is triggered as the actions_intent_COMPLETE_PURCHASE. This event includes a purchaseStatus field that describes the result of the user's purchase flow. This status isn't automatically surfaced to the user, so your webhook presents a descriptive message based on which status is received.

Your fulfillment should handle the following statuses:

  • PURCHASE_STATUS_OK: The purchase was successful.
    • Since the transaction is complete, you should either end the conversation or exit the transactional flow.
  • PURCHASE_STATUS_ALREADY_OWNED: The user already owns that item, so the purchase was unsuccessful.
  • PURCHASE_STATUS_ITEM_UNAVAILABLE: The requested item is not available at the time of the user's purchase.
  • PURCHASE_STATUS_ITEM_CHANGE_REQUESTED: The user decided to purchase something else.
    • The best user experience here is to reprompt with your item selection so the user can make another decision right away.
  • PURCHASE_STATUS_USER_CANCELLED: The user cancelled the purchase flow.
    • Since the user prematurely exited the flow, you should 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.
    • You should handle this error by letting 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.
    • You should handle this error by letting the user know the transaction failed, and ask if they want to try again.
// This intent name is arbitrary, the actions_intent_COMPLETE_PURCHASE event
// is what matters
app.intent('actions.intent.COMPLETE_PURCHASE', (conv) => {
  const arg = conv.arguments.get('COMPLETE_PURCHASE_VALUE');
  console.log('User Decision: ' + JSON.stringify(arg));
  if (!arg || !arg.purchaseStatus) {
    conv.close('Purchase failed. Please check logs.');
    return;
  }
  if (arg.purchaseStatus === 'PURCHASE_STATUS_OK') {
    conv.close('Purchase completed! You\'re all set!');
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ALREADY_OWNED') {
    conv.close('Purchase failed. You\'ve already owned the item.');
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ITEM_UNAVAILABLE') {
    conv.close('Purchase failed. Item is not available.');
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ITEM_CHANGE_REQUESTED') {
    // Reprompt with your item selection dialog
  }  } else {
    conv.close('Purchase Failed:' + arg.purchaseStatus);
  }
});