Digital Goods Transaction of Actions on Google (Write Code and Test)

Yoichiro Tanaka
Google Developer Experts
10 min readDec 15, 2018

From the Part 1 to the Part 6, I described about how to prepare to use Digital Goods Transactions feature. Now, you should have an environment to test the feature. Finally, you start implementing your action with the Digital Goods Transactions from this story.

Let’s get started.

Transaction Flow

I have already described about the transaction flow of the Digital Goods Transactions feature. Again, I introduce you the flow here.

  1. Gather information
  2. Build order
  3. Complete the purchase
  4. Describe the purchase status
  5. Make the purchase repeatable

Prerequisite

Now, you should have your Actions on Google project and Dialogflow agent. But, I don’t describe about a fulfillment so far. I don’t describe about how to create the fulfillment here. Instead, please read the following guide:

I assume that you have your fulfillment code and can deploy it to some server you have or some cloud service and you configured the fulfillment URL to your Dialogflow agent. For example, your fulfillment URL should be set as like the following:

I assume that your fulfillment code is the following:

"use strict";const functions = require("firebase-functions");
const {
dialogflow,
List,
CompletePurchase
} = require("actions-on-google");
const request = require("request");
const {google} = require("googleapis");
const serviceAccount = require("<YOUR_SERVICE_ACCOUNT_PATH>");
const packageName = "<YOUR_ANDROID_APP_PACKAGE_NAME>";
const app = dialogflow({
debug: true
});
// TODO: Write code here.exports.digitalGoodsTest = functions.https.onRequest(app);

The <YOUR_SERVICE_ACCOUNT_PATH is the service account JSON file path that you retrieved from Google Cloud Platform Console at the Part 5. And, the <YOUR_ANDROID_APP_PACKAGE_NAME> is the package name that you decided at creating your Android app at the Part 2 (I used “jp.eisbahn.digitalgoodstest” as sample at the time).

Dependent libraries are the following:

  • actions-on-google
  • firebase-functions
  • firebase-admin
  • request
  • googleapis

Add these libraries to your fulfillment project by npm install or yarn add.

I assume your situation that you can invoke your action in the Actions simulator as like the following:

Notice that you need to integrate your Dialogflow agent to your Actions on Google project by using the “Integration” menu on the left navigation menu of the Dialogflow Console.

Gather information

First, gather information about your Digital Goods from Google Play. Here, you create an intent to order the gathering available Digital Goods and implement the intent handler function into your fulfillment code.

Click the “+” icon next to the “Intents” menu item on the left navigation menu of the Dialogflow Console. Then, a page to create a new intent is opened. Fill in values to each field as like the following:

  • Intent name: Type the “Gather information”.
  • Training Phrases: Type “Gather information”.
  • Fulfillment: Turn on the “Enable webhook call for this intent”.

Click the “SAVE” button on the top of the page.

Okay, let’s start writing the intend handler code. You write three helper functions, then write the handler function. The first helper function is to create JWT client object.

const createJwtClient = () => {
const scopes = [
"https://www.googleapis.com/auth/actions.purchases.digital"
];
return new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes,
null
);
};

The second helper function is to get SKUs information from the Google Play with Actions API.

const getSkus = (tokens, conv) => {
return new Promise((resolve, reject) => {
const url = `https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`;
const convId = conv.request.conversation.conversationId;
const param = {
conversationId: convId,
skuType: "SKU_TYPE_IN_APP",
ids: [
"premium",
"coins"
]
};
request.post(url, {
auth: {
bearer: tokens.access_token
},
json: true,
body: param
}, (err, httpResponse, body) => {
if (err) {
reject(err);
} else {
const statusCode = httpResponse.statusCode;
const statusMessage = httpResponse.statusMessage;
console.log(`${statusCode}: ${statusMessage}`);
console.log(JSON.stringify(body));
resolve(body);
}
});
});
};

The code above accesses to the Actions API with the request library. The API request has the following parameters:

  • conversationId: The conversation ID you can retrieve the conv argument.
  • skuType: The SKU type string you want to get products. If you want to access to managed products, you specify SKU_TYPE_IN_APP. On the other hand, if you want to access to subscriptions, you need to use SKU_TYPE_SUBSCRIPTION.
  • ids: The array of product IDs you want to know.

You need to set an access token to the request header Authorization at calling the Actions API.

Build Order

Second, you need to build the response based on retrieved skus’ information so that the user can order products your action offers.

The code below is the third helper function to build and respond with the gathered information.

const respondSkus = (conv, body) => {
const skus = body.skus || [];
if (skus.length > 0) {
const list = {
title: "Products",
items: {}
};
skus.forEach(sku => {
const key = `${sku.skuId.skuType},${sku.skuId.id}`;
const description = sku.description;
const price = sku.formattedPrice;
list.items[key] = {
title: sku.title,
description: `${description} | ${price}`
};
});
list.items["cancel"] = {
title: "Cancel",
description: "Cancel purchase"
};
conv.ask("Which product do you want to order?");
conv.ask(new List(list));
} else {
conv.ask("No products.");
}
};

In the code above, you build a response with the List visual component. Each item of the list has a key to identify each product. The key consists of two elements: SKU Type and SKU ID.

And, the intent handler function with three helper functions above is the following:

app.intent("Gather information", conv => {
const SCREEN_OUTPUT = 'actions.capability.SCREEN_OUTPUT';
if (!conv.surface.capabilities.has(SCREEN_OUTPUT)) {
conv.ask("Sorry, try this on a screen device or " +
"select the phone surface in the simulator.");
return;
}
return new Promise((resolve, reject) => {
createJwtClient().authorize((err, tokens) => {
if (err) {
reject(`Auth error: ${err}`);
} else {
getSkus(tokens, conv).then(body => {
respondSkus(conv, body);
resolve();
}).catch(err => {
reject(`API request error: ${err}`);
});
}
});
});
});

The first thing you should do is to check the user’s surface. If the user’s device doesn’t have a screen, your action rejects the purchase request and respond the message that represents the situation.

If the user’s device is supporting a screen, your action can go forward to the purchase. You should have the service account key JSON file issued by the previous part. You cannot use the JSON file to access to the Actions API directly. Instead, you can issue an access token from the service account key JSON file. To do that, you need to call the authorize function of the result of the createJwtClient function. If the issuing access token is successfully, your action gets SKU information with the getSkus function and responds them with respondSkus function.

Complete the purchase

Third, you need to implement the handler when the user taps a product from the list. In the handler function, you write a code to request the transaction with the CompletePurchase object in this section.

When the user taps an item of the list, the Actions on Google sends an actions_intent_OPTION event to the Dialogflow agent. You add a new intent to handle the event on the Dialogflow Console. Click the “+” icon next to the “Intents” menu item on the left navigation menu of the Dialogflow Console. Then, a page to create a new intent is opened. Fill in values to each field as like the following:

  • Intent name: Type the “actions.intent.OPTION”.
  • Events: Type the “actions_intent_OPTION”.
  • Fulfillment: Turn on the webhook.

Click the “SAVE” button to reflect the filled in values.

Okay, let’s start to write the handler function. Add the following code to your fulfillment code:

app.intent("actions.intent.OPTION", (conv, params, option) => {
if (option !== "cancel") {
const [skuType, id] = option.split(",")
conv.ask(new CompletePurchase({
skuId: {
skuType: skuType,
id: id,
packageName: packageName
}
}));
} else {
conv.ask("Canceled");
}
});

This code checks whether the user tapped the “Cancel” item or not. If no, it retrieves the SKU type and SKU ID from the 3rd argument of the intent handler, then responds the CompletePurchase object. You need to specify three parameters to the object: SKU type, SKU ID and package name of your Android app.

After responding the CompletePurchase object, the Google Assistant shows the user the order information including the price and asks the user whether actually buy or not.

Describe the Purchase Status

The user decides whether accepts and buys the order or not. After that, the Actions on Google sends the actions_intent_COMPLETE_PURCHASE event to your Dialogflow agent with the purchase status.

Your action need to confirm the status and need to respond the user to describe the status of the purchase.

To receive the actions_intent_COMPLETE_PIRCHASE event, create a new intent on the Dialogflow Console. Click the “+” icon next to the “Intents” menu item on the left navigation menu of the Dialogflow Console. Then, a page to create a new intent is opened. Fill in values to each field as like the following:

  • Intent name: Type the “actions.intent.COMPLETE_PURCHASE”.
  • Events: Type the “actions_intent_COMPLETE_PURCHASE”.
  • Fulfillment: Turn on the webhook.

And, add the code below to handle the intent.

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 are all set!");
} else if (arg.purchaseStatus === "PURCHASE_STATUS_ALREADY_OWNED") {
conv.close("Purchase failed. You have 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") {
conv.close("Purchase failed. Item change requested.");
} else {
conv.close("Purchase Failed:" + arg.purchaseStatus);
}
});

The purchase result can be retrieved from the conv.arguments.get("COMPLETE_PURCHASE_VALUE"). And you can get the purchase status by calling the arg.purchaseStatus. Each value represents each purchase status like the following:

  • PURCHASE_STATUS_OK: The purchase could be completed.
  • PURCHASE_STATUS_ALREADY_OWNED: The user have already purchase the product.
  • PURCHASE_STATUS_ITEM_UNAVAILABLE: The product the user tried to buy is not currently available.
  • PURCHASE_STATUS_ITEM_CHANGE_REQUESTED: The user decided to purchase something else.
  • PURCHASE_STATUS_USER_CANCELLED: The user cancelled the purchase flow.
  • PURCHASE_STATUS_ERROR: The transaction failed for an unknown reason.
  • PURCHASE_STATUS_UNSPECIFIED: The transaction failed for an unknown reason, resulting in an unknown status.

In this sample, your action closes the conversation after responding the message decided according to each purchase status.

Okay, after writing all codes, deploy it to your server/cloud service.

Release your Action as Alpha

Before testing your action, you need to release your action as Alpha. Click the “Release” menu item on the left navigation menu of the Actions on Google Console. The expand the “Alpha” section, and click the “SUBMIT FOR ALPHA” button.

The agreement page is opened. Turn on all check boxes, and click the “SUBMIT” button.

Furthermore, you need to apply the latest snapshot (called as “Draft”) as the target for test. Click the “Simulator” menu on the left navigation menu, and click the “CHANGE VERSION” button on the top of the page.

In the opened dialog, select the “VERSION - Draft” as the target version, and click the “DONE” button.

Well done! All preparations are ready to test your action.

Test Your Action

Now, you can test your action. Google recommends to use Android device to test Digital Goods Transactions. Launch the Google Assistant on your Android, then say the “Talk to <YOUR_ACTION_NAME>” to the Assistant.

And, say the “gather information” to the Assistant.

Tap the “Premium (Digital Goods Test)” in the list.

Tap the “Buy now” suggestion chip.

To complete the purchase, put your finger on the sensor or input your password.

After several tens of seconds,

You see the receipt for the purchase. Tap the “Continue” suggestion chip.

Congratulation! The purchase was completed and your action received the PURCHASE_STATUS_OK value.

Next Action

Finally, you could implement your action with Digital Goods Transactions.That is, you already know how to build your action with the simple scenario using Digital Goods Transactions.

In the next story, I intend to describe other pieces of the Digital Goods Transactions.

--

--