Digital Goods Transaction of Actions on Google (Consume Products)

Yoichiro Tanaka
Google Developer Experts
4 min readDec 16, 2018

As a last story, I describe about how to implement a feature to consume digital goods in this story.

Consumable? Non-consumable?

Your action can support both product types: Managed products and Subscription products. Especially, there are further two types for the managed products:

  • Consumable products: They can be purchased multiple times, such as a quantity of in-game currency. After the item is purchased, send a consume request to make the item available for purchase again.
  • Non-consumable products: They can only be purchased once per user, such as a paid upgrade for an ad-free experience or a game expansion with additional content.

There is no difference between them on the Google Play Console. Instead, your action needs to handle the difference.

Check Products User already Purchased

When a user accesses to your action after the user purchased your products, Actions on Google includes the purchased product information into each request to your action. You can retrieve the product information by the following helper function:

const findEntitlement = (conv, skuType, id) => {
const entitlementGroups = conv.user.entitlements;
const entitlementGroup = entitlementGroups.find(x => {
return x.packageName === packageName;
});
if (entitlementGroup) {
return entitlementGroup.entitlements.find(x => {
return x.skuType === skuType.substring(9)
&& x.sku === id;
});
}
return undefined;
};

In the protocol of the Actions on Google Webhook, products the user already purchased are called “Entitlement”. You can get them by conv.user.entitlements. The class of the result is an array of GoogleActionsV2PackageEntitlement. That is, you can retrieve multiple entitlements per Android app’s package name.

Each GoogleActionsV2PackageEntitlement has one or more GoogleActionsV2Entitlement that you can access to them by the elements property. The class has SKU’s ID and the type. This means that passed entitlements represent products the user already purchased. Your action can know the list of sets including package name, SKU ID and SKU type.

PURCHASE_STATUS_ALREADY_OWNED

When users try to purchase some product that was already purchased (this means that your action responds the CompletePurchase object that has the product’s SKU ID and the type), the Actions on Google returns the PURCHASE_STATUS_ALREADY_OWNED status as the result. That is, the purchase request was failed.

Non-consumable products mean that the products can be bought once only. Therefore, for non-consumable products, this behavior is suitable. In other words, basically all requests with the CompletePurchase are as non-consumable purchase.

Re-purchase Managed Products

For consumable products, it is necessary to use other way to re-purchase (aka. consume) managed products. The Actions on Google provides an API to consume products. The API is a part of the Actions API.

You should already have a service account to use the Actions API to retrieve products’ information from Google Play. You can use the same service account to consume products.

The code to consume products is the following:

const consume = (tokens, conv, entitlement) => {
return new Promise((resolve, reject) => {
const convId = conv.request.conversation.conversationId;
const purchaseToken = entitlement.inAppDetails.inAppPurchaseData.purchaseToken;
const url = `https://actions.googleapis.com/v3/conversations/${convId}/entitlement:consume`;
request.post(url, {
auth: {
bearer: tokens.access_token
},
json: true,
body: {
purchaseToken
}
}, (err, httpResponse, body) => {
if (err) {
reject(err);
} else {
const statusCode = httpResponse.statusCode;
const statusMessage = httpResponse.statusMessage;
console.log(`${statusCode}: ${statusMessage}`);
resolve();
}
});
});
};

Each entitlement has a purchase token to consume it after purchased. You can get the token by inAppDetails.inAppPurchaseData.purchaseToken of the entitlement object. You need to specify the purchase token into the request body of the endpoint to consume products.

Support Consumable and Non-consumable

As the result, you can update your actions_intent_OPTION intent handle function as like the following:

const consumableProducts = ["coins"];app.intent("actions.intent.OPTION", (conv, params, option) => {
if (option === "cancel") {
conv.ask("Canceled");
return;
}
const [skuType, id] = option.split(",");
const entitlement = findEntitlement(conv, skuType, id);
if (entitlement && consumableProducts.includes(id)) {
return new Promise((resolve, reject) => {
createJwtClient().authorize((err, tokens) => {
if (err) {
reject(`Auth error: ${err}`);
return;
}
consume(tokens, conv, entitlement).then(() => {
conv.close(`You purchased ${id} successfully.`);
resolve();
}).catch(err => {
reject(`API request error: ${err}`);
});
});
});
} else {
conv.ask(new CompletePurchase({
skuId: {
skuType: skuType,
id: id,
packageName: packageName
}
}));
}
});

Conclusion

To explain how to use Digital Goods Transactions, it was necessary to write 9 stories… I think that the specification of the Digital Goods Transactions is so bad. Too many steps. Too complex. And, Too difficult. Also, the official document doesn’t provide enough information. I guess that most developers cannot implement your actions with the Digital Goods Transactions by reading the official document only.

I expect that you can implement your action with Digital Goods Transactions and can monetize in your action with it by reading stories I wrote.

--

--