Add ISP Support to Your Skill Code
When you implement in-skill purchasing (ISP) in your skill, you will need to do the following:
- Call the in-skill product service to get the product inventory
- Provide custom intents to enable purchases and handlers for those intents in your skill code
- Send directives from your skill code to the purchase flow specifying a product for purchase
- Resume your skill after the purchase flow completes
- For consumable purchases, save the user's inventory of items in persistent storage and manage the inventory as the user uses the items
This topic provides the steps, code examples, best practices and links to more information.
- Prerequisites
- Get the in-skill products list
- Add support for purchase requests
- Offer purchase suggestions
- Resume your skill after the purchase flow
- Handle a refund or cancel request
- Manage the inventory for consumable purchases
- Sample code
- Related topics
Prerequisites
In order to complete the steps in this document you should have:
- A skill with a custom interaction model, and associated Lambda function. For details, see Steps to Build a Custom Skill. Note that you can add in-skill products to skills hosted as web services, but the code in this topic demonstrates how to add in-skill products to an AWS Lambda function.
- A skill with at least one product added to your custom skill. You can configure these products in the developer console) or with the ASK CLI.
- Designed your in-skill purchasing customer experience. For details, see Design a Good Customer Experience for In-Skill Purchasing.
Get the in-skill products list
To offer products in your skill, you need to get the inventory of products available to the user in the current skill session. When you get the list, use it to:
- Know the product ID for each product. You will need to map user requests for products to product IDs
- Know what products can be offered for sale to this user (
PURCHASABLE
) - Know what this user has already purchased (products marked
ENTITLED
) - For consumable products, know how many units of the product this user has purchased as of today (
activeEntitlementCount
)
You get the list with a HTTP GET request to the InSkillProducts
API of the in-skill product service. Add this call to your launch request handler, and store this list of products during the skill session.
If the request is successful, the message result will contain an InSkillProducts object that contains a list of InSkillProduct objects.
Example
This sample code uses the Alexa Skills Kit SDK for Node.js (v2).
This LaunchRequest
handler uses the MonetizationServiceClient provided in the SDK to call the InSkillProducts
API and get the list of products. The welcome message then tells the user which products they have already purchased.
/*
Function to demonstrate how to filter inSkillProduct list to get list of
all entitled products to render Skill CX accordingly
*/
function getAllEntitledProducts(inSkillProductList) {
const entitledProductList = inSkillProductList.filter(record => record.entitled === 'ENTITLED');
return entitledProductList;
}
/*
Helper function that returns a speakable list of product names from a list of
entitled products.
*/
function getSpeakableListOfProducts(entitleProductsList) {
const productNameList = entitleProductsList.map(item => item.name);
let productListSpeech = productNameList.join(', '); // Generate a single string with comma separated product names
productListSpeech = productListSpeech.replace(/_([^_]*)$/, 'and $1'); // Replace last comma with an 'and '
return productListSpeech;
}
/*
Request handler. This handler is used when the user starts the skill without
specifying a specific intent.
*/
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const locale = handlerInput.requestEnvelope.request.locale;
const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
return ms.getInSkillProducts(locale).then(
function reportPurchasedProducts(result) {
const entitledProducts = getAllEntitledProducts(result.inSkillProducts);
if (entitledProducts && entitledProducts.length > 0) {
// Customer owns one or more products
return handlerInput.responseBuilder
.speak(`Welcome to ${skillName}. You currently own ${getSpeakableListOfProducts(entitledProducts)}` +
' products. To hear a random fact, you could say, \'Tell me a fact\' or you can ask' +
' for a specific category you have purchased, for example, say \'Tell me a science fact\'. ' +
' To know what else you can buy, say, \'What can i buy?\'. So, what can I help you' +
' with?')
.reprompt('I didn\'t catch that. What can I help you with?')
.getResponse();
}
// Not entitled to anything yet.
console.log('No entitledProducts');
return handlerInput.responseBuilder
.speak(`Welcome to ${skillName}. To hear a random fact you can say 'Tell me a fact',` +
' or to hear about the premium categories for purchase, say \'What can I buy\'. ' +
' For help, say , \'Help me\'... So, What can I help you with?')
.reprompt('I didn\'t catch that. What can I help you with?')
.getResponse();
},
function reportPurchasedProductsError(err) {
console.log(`Error calling InSkillProducts API: ${err}`);
return handlerInput.responseBuilder
.speak('Something went wrong in loading your purchase history')
.getResponse();
},
);
},
}; // End LaunchRequestHandler
This sample code uses the Alexa Skills Kit SDK for Node.js (v1).
This LaunchRequest
handler constructs an HTTP GET request to call the InSkillProducts
API and get the list of products. The getProductsAndEntitlements
function takes a callback function parameter, makes the HTTP call asynchronously and then calls the callback function. The callback function processes the results once the HTTP call completes.
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
...
// call a function to get the available products
getProductsAndEntitlements(this, functionToProcessProductListOnceItIsLoaded);
...
}
function getProductsAndEntitlements(self, callback) {
// Invoke the entitlement API to load products only if not already cached
if (!self.attributes.areProductsLoaded) {
self.attributes.inSkillProducts = [];
var returnData = [];
// Information required to invoke the API is available in the session
const https = require('https');
const apiEndpoint = "api.amazonalexa.com";
const token = "bearer " + self.event.context.System.apiAccessToken;
const language = self.event.request.locale;
// The API path
const apiPath = "/v1/users/~current/skills/~current/inSkillProducts";
const options = {
host: apiEndpoint,
path: apiPath,
method: 'GET',
headers: {
"Content-Type" : 'application/json',
"Accept-Language" : language,
"Authorization" : token
}
};
// Call the API
const req = https.get(options, (res) => {
res.setEncoding("utf8");
if(res.statusCode != 200) {
console.log("InSkillProducts returned status code " + res.statusCode);
self.emit(":tell", "Something went wrong in loading the purchase history. Error code " + res.code );
}
res.on('data', (chunk) => {
console.log("Chunk:" + chunk);
returnData += chunk;
});
res.on('end', () => {
var inSkillProductInfo = JSON.parse(returnData);
if(Array.isArray(inSkillProductInfo.inSkillProducts))
self.attributes.InSkillProducts = inSkillProductInfo.inSkillProducts;
else
self.attributes.InSkillProducts=[];
console.log("Product list loaded:" + JSON.stringify(self.attributes.InSkillProducts));
callback(self, self.attributes.InSkillProducts);
});
});
req.on('error', (e) => {
console.log('Error calling InSkillProducts API: ' + e.message);
self.emit(":tell", "Something went wrong in loading the product list. Error code " + e.code + ", message is " + e.message);
});
} // End if (!self.attributes.areProductsLoaded) {}
else {
console.log("Product info already loaded.");
callback(self, self.attributes.InSkillProducts);
return;
}
}
// Process the product list.
var functionToProcessProductListOnceItIsLoaded = function(self, inSkillProductList) {
if (!inSkillProductList) {
console.log("Something went wrong in loading product list.");
}
// Do something with the retrieved product list
for (var idx = 0; idx < inSkillProductList.length; idx ++) {
console.log("inSkillProductList[" + idx + "] is:" + JSON.stringify(inSkillProductList[idx]));
}
}
Add support for purchase requests
You will need to add support for a user to shop products and buy one by name in your skill. For example, you would want to support requests like the following:
- What can I buy?
- What can I shop for?
- Tell me what I can buy
- I want to buy product name
- I want product name
- Alexa, ask skill invocation name to give me product name
To support purchase requests, you:
- Build a custom intent to support a purchase request
- Add code to handle the custom intent, and start a purchase flow by sending a directive
Build the intent
The following JSON is an example of how you could model custom intents to support a user request to buy a product by name, or to find products to buy.
Example
{
"intents": [
{
"name": "WhatCanIBuyIntent",
"samples": [
"what can I buy",
"what can I shop for",
"tell me what I can buy",
"buy",
"shop",
"purchase"
],
"slots": []
},
{
"name": "BuyIntent",
"samples": [
"buy {ProductName}",
"purchase {ProductName}",
"want {ProductName}",
"would like {ProductName}"
],
"slots": [
{
"name": "ProductName",
"type": "LIST_OF_PRODUCT_NAMES"
}
]
}
],
"types": [
{
"name": "LIST_OF_PRODUCT_NAMES",
"values": [
{
"id": "reference_name",
"name": {
"value": "Product A",
"synonyms": [
"A product"
]
}
}
]
}
]
}
Handle the intent
When you handle the buy intent, there are two scenarios your code must handle.
The user doesn't specify a product
The user may ask to shop the products you offer. In this case, you should direct the user to specify one product. In other words, your code detects a generic shop or buy request and responds to the user with a list of eligible products to choose from. When you give a list:
- Make options easier to remember by listing only 2 or 3 at a time
- Use periods or break SSML tags instead of commas to clearly distinguish one option from another. For more information, see the Lists section of the design guide
For example:
User: What can I buy?
Alexa: I have 2 expansion packs available. Cave Quest or Deep Sea. Which are you interested in?
The user specifies a product
The user may specify a product directly, or you may direct them to choose a product. Regardless, once the user specifies the product to buy, you then initiate the purchase flow by sending a Buy
directive.
Your skill session ends when the purchase flow starts. Amazon handles the voice interaction model and all the mechanics of the purchase, as well as obtaining the product description and list price from the product's schema. The message and price are automatically adjusted for Prime customers. When the purchase completes your skill will be re-launched with a new session, and a purchase result is supplied to your skill.
Buy
directive, be sure to save any relevant user data in a persistent data store so that your skill can continue where the user left off after the purchase flow is completed and your endpoint is back in control of the user experience. You can use any persistent storage you want. The ASK SDKs for Node.js, Java, and Python all include a PersistanceAdapter
to save attributes. For details, see
ASK SDK documentation about persistent attributes.Example
The following example shows how you might add code to a Lambda function to send the Connections.SendRequest
directive for a buy request.
This sample code uses the Alexa Skills Kit SDK for Node.js (v2).
return handlerInput.responseBuilder
.addDirective({
type: "Connections.SendRequest",
name: "Buy",
payload: {
InSkillProduct: {
productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
}
},
token: "correlationToken"
})
.getResponse();
This sample code uses the Alexa Skills Kit SDK for Node.js (v1).
this.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Buy',
'payload': {
'InSkillProduct': {
'productId': 'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
}
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
JSON syntax for a Connections.SendRequest
directive for a purchase request. In this case, the name
is Buy
.
{
"directives": [
{
"type": "Connections.SendRequest",
"name": "Buy",
"payload": {
"InSkillProduct": {
"productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
},
"token": "correlationToken"
}
]
}
Directive fields
Field | Type | Description | Required |
---|---|---|---|
type |
String | The type of directive. AlwaysSendRequest for a purchase flow |
Yes |
name |
String | Indicates the target for the SendRequest message. Always Buy for a purchase request. |
Yes |
payload |
Object | Contains details for the specified action. For a purchase request, this includes the InSkillProduct property with a product ID. |
Yes |
token |
String | A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response . You provide this token in a format that makes sense for the skill. |
Yes |
shouldEndSession |
Boolean | Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. |
Yes |
Offer purchase suggestions
Another way for users to buy your in-skill products is with a purchase suggestion, which means you proactively offer products related to how the user is currently interacting with your skill. You would check whether a user owns a product from the saved list and pass the product you want to suggest as well as a message relevant to that product to Amazon's purchase flow. To offer purchase suggestions, you:
- Add code that starts the purchase flow with a directive.
For example:
After the user finishes content, the skill sends a directive that indicates an Upsell request. The skill session ends, and Alexa speaks the upsellMessage included in the directive.
Alexa: If you'd like more adventures to play, you can now get the Cave Quest expansion pack. Wanna learn more?
The skill sends a directive that indicates an Upsell
request. This ends the current skill session.
User: Yes
For entitlements, Amazon provides the purchasePromptDescription
and price before confirming the purchase.
For subscriptions, Amazon provides the price before confirming the purchase.
Send a directive to start the purchase suggestion
When you want to respond to a user intent with a purchase suggestion, you send a Connections.SendRequest
directive to Alexa that:
- Indicates a purchase suggestion by specifying
upsell
- Provides the identifier for the suggested product
- Provides a message prompt for the purchase suggestion, the
upsellMessage
- Contains a token to store the current state of the skill or other relevant information. This is passed back to your skill in the response from the
SendRequest
call.
For the upsellMessage
:
- Never include price or offer details. Be careful not to reference the list price, subscription term, or free trial length in any of the skill content. It is important that these values are obtained from your product description files. For example, Amazon may offer a discounted price if the user is a Prime member.
- Determine whether a user is interested by making sure the message ends with an explicit confirmation question. Also do not ask whether they'd like to buy. Users make a buying decision after this step.
- Help, don't sell. Offer your product as a solution. Explain why your product is relevant at this moment, and what it does for the user.
- Avoid suggesting products too often. You risk losing users when your suggestions begin to feel like interruptions. Start conservatively, than change the frequency over time to find the best approach.
- Only make one suggestion. If a user isn't interested, continue where they left off. Don't suggest another product.
For more information about how to offer purchase suggestions, see How to Make a Purchase Suggestion.
Your skill session ends when the purchase flow starts. Amazon handles the voice interaction model and all the mechanics of the purchase, as well as obtaining the product description and list price from the product's schema. The message and price are automatically adjusted for Prime customers. When the purchase completes your skill will be re-launched, and a purchase result is supplied to your skill.
Upsell
directive, be sure to save any relevant user data in a persistent data store so that your skill can continue where the user left off after the purchase flow is completed and your endpoint is back in control of the user experience. You can use any persistent storage you want. The ASK SDKs for Node.js, Java, and Python all include a PersistanceAdapter
to save attributes. For details, see
ASK SDK documentation about persistent attributes.Example
The following example shows how you might add code to a Lambda function to send the Connections.SendRequest
directive for a purchase suggestion.
This sample code uses the Alexa Skills Kit SDK for Node.js (v2).
return handlerInput.responseBuilder
.addDirective({
type: "Connections.SendRequest",
name: "Upsell",
payload: {
InSkillProduct: {
productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
},
upsellMessage: "This is my product...Do you want to know more?",
},
token: "correlationToken",
})
.getResponse();
This sample code uses the Alexa Skills Kit SDK for Node.js (v1).
this.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Upsell',
'payload': {
'InSkillProduct': {
'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
},
'upsellMessage': 'This is my product...Do you want to know more?'
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
JSON syntax for a Connections.SendRequest
directive for an upsell. In this case, the name
is Upsell
and the payload
includes the upsellMessage
.
{
"directives": [
{
"type": "Connections.SendRequest",
"name": "Upsell",
"payload": {
"InSkillProduct": {
"productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
},
"upsellMessage": "This is my product...Do you want to know more?"
},
"token": "correlationToken"
}
]
}
Directive Fields
Field | Type | Description | Required |
---|---|---|---|
type |
String | The type of directive. AlwaysSendRequest for a purchase flow |
Yes |
name |
String | Indicates the target for the SendRequest message. Always Upsell for a purchase suggestion. |
Yes |
payload |
Object | Contains details for the specified action. For a purchase suggestion, this includes the InSkillProduct property with a product ID and the upsellMessage property with a custom message. |
Yes |
upsellMessage |
String | A product suggestion that fits the current user context. Should always end with an explicit confirmation question. | Yes |
token |
String | A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response . You provide this token in a format that makes sense for the skill. |
Yes |
shouldEndSession |
Boolean | Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. |
Yes |
Resume your skill after the purchase flow
You need to correctly handle the result of a purchase flow and resume your skill so the user experience is smooth.
To do this:
- You handle the
Connections.Response
request
The result of the purchase flow is a Connections.Response
request that indicates whether the purchase was successful, and the token
passed in the request, which you can use to help you resume where the user left off.
Use the guidelines in the sections that follow for details about how to handle each result type.
Example
The following JSON shows a Connections.Response
request from a product offer.
{
"type": "Connections.Response",
"requestId": "string",
"timestamp": "string",
"name": "Upsell",
"status": {
"code": "string",
"message": "string"
},
"payload": {
"purchaseResult":"ACCEPTED",
"productId":"string",
"message":"optional additional message"
},
"token": "string"
}
Connection.Response Request Fields
Name | Type | Description | Required |
---|---|---|---|
type |
String | The type of directive. Always Connections.Response for a purchase flow response |
Yes |
requestId |
String | The unique identifier for this request | Yes |
timeStamp |
String | The time that this request was issued | Yes |
name |
String | Indicates the target for the SendRequest message: Upsell for a purchase suggestion, Buy for a buy request, or Cancel for a cancellation or refund request. |
Yes |
status |
Object | Provides details of the HTTP status | Yes |
status.code |
String | The HTTP response status such as 200, 400, 404 | |
status.message |
String | The HTTP response message such as OK, Not Found, Not Authorized, etc. | |
payload |
Object | Contains details specific to the purchase transaction | Yes |
payload.purchaseResult |
string | Indicates the result of the purchase transaction. Either ACCEPTED, DECLINED, ALREADY_PURCHASED, or ERROR. | Yes |
payload.productId |
String | In-skill product that was sent in the buy or contextual message | Yes |
payload.message |
String | Contains additional details about the transaction. | No |
token |
String | The token passed by the skill when making the purchase request. | Yes |
You should check the status of the product returned in the purchaseResult
using the InSkillProduct API, and use the following guidelines to handle the purchaseResult
.
ACCEPTED result
What Alexa says:
- One time purchase or consumable: "Great! You have successfully purchased in-skill product name"
- Subscription: "Great. You now have in-skill product name"
What your skill should do:
Automatically start requested content or use the requested consumable, for example, "Let's play it now…" or "Here's your hint…" If the user commits to a purchase, make sure you complete their original request.
For a consumable product, your skill also needs to update the user's inventory of items in persistent storage associated with the userId
provided in the request. See
Manage the inventory for consumable purchases.
Example:
User completes their purchase
Alexa: You now have Cave Quest
Your skill launches
Alexa: Let's play it now
DECLINED result
What Alexa says:
- One time purchase or consumable: "Okay"
- Subscription: "No problem. You can ask me to sign up for in-skill product name anytime."
What your skill should do:
Pick up where the user left off and offer a way for the user to continue.
Example:
User declines the purchase
Alexa: Okay
Your skill launches and says:
Alexa: Let's choose an adventure from your collection. Which would you like to play?
ERROR result
What Alexa says:
The response depends on why the error occurred.
- "Hmm, I had a problem with your payment method. To update, checkout the link I sent to your Alexa app."
- "Sorry, I'm having trouble with that right now."
- "I'm sorry. content title isn't available in your country."
- "Sorry, I'm not able to make in-skill purchases on this device."
What your skill should do:
- Pick up where the user left off, offer a way for them to continue.
- Avoid mentioning that an error occurred. The user receives an explanation from the purchase flow before they return to your skill.
- Do not ask the user if they'd like to try again.
Example:
User agrees to purchase, but needs to update payment information
Alexa: Hmm, I had a problem with your payment method. To update, checkout the link I sent to your Alexa app.
Your skill launches
Alexa: Let's choose an adventure from your collection. Which would you like to play?
ALREADY_PURCHASED result
This result can be returned for one-time purchases and subscriptions.
This result is never returned for consumables since consumable products can be purchased multiple times.
What Alexa says:
Good news, you already have in-skill product name
What your skill should do:
- Automatically start the requested content.
- Do not offer a different product
Example:
User asks to buy Crystal Catchers, but they already own it.
Alexa: Good news. You already own Crystal Catchers.
Your skill launches
Alexa: Let's choose an adventure from your collection. Which would you like to play?
Handle a refund or cancel request
You must also be able to handle a user request to refund a purchase and forward the request to the purchase flow.
A user might ask for a refund in one of the following ways:
- Alexa, tell skill invocation name to return product name
- Alexa, refund product name
- I want to return product name
- I want a refund for product name
Example:
User: Alexa, refund Cave Quest
Skill sends a Connections.SendRequest
directive that indicates a Cancel
request. This ends the current skill session.
Alexa: For a refund, check out the link I sent to your Alexa app.
Or a user might ask to cancel a subscription in one of the following ways:
- Cancel my subscription for product name
- Cancel the subscription for product name
Example:
User: Alexa, cancel my Treasure Finder Plus subscription.
Skill sends a Connections.SendRequest
directive that indicates a Cancel
request. This ends the current skill session.
Alexa: Okay. As a reminder, Treasure Finders Plus includes a collection of over 10 exclusive adventures, with a new one added each month. Are you sure you want to cancel your subscription?
To support cancellation or refund requests, you:
- Build a custom intent to support a refund/cancellation request
- Add code to handle the custom intent, and start a cancellation flow by sending a directive
Build the intent
The following JSON is an example of how you could model the custom intent to support a user request to refund a purchase. The sample intent lists variants to return or refund a product, and a custom slot that contains a list of the in-skill products.
Amazon.CancelIntent
and end the skill session.Example
{
"intents": [
{
"name": "RefundSkillItemIntent",
"samples": [
"return {ProductName}",
"refund {ProductName}",
"want a refund for {ProductName}",
"would like to return {ProductName}"
],
"slots": [
{
"name": "ProductName",
"type": "LIST_OF_PRODUCT_NAMES"
}
]
}
],
"types": [
{
"name": "LIST_OF_PRODUCT_NAMES",
"values": [
{
"id": "reference_name",
"name": {
"value": "Product A",
"synonyms": [
"A product"
]
}
}
]
}
]
}
Handle the intent
In your handler for the refund/cancel intent, you should send a message to the purchase flow that indicates a cancellation.
Cancel
directive, be sure to save any relevant user data in a persistent data store so that your skill can continue where the user left off after the purchase flow is completed and your endpoint is back in control of the user experience. You can use any persistent storage you want. The ASK SDKs for Node.js, Java, and Python all include a PersistanceAdapter
to save attributes. For details, see
ASK SDK documentation about persistent attributes.The following example shows how you might add code to send a Connections.SendRequest
directive for a refund request.
This sample code uses the Alexa Skills Kit SDK for Node.js (v2).
return handlerInput.responseBuilder
.addDirective({
type: 'Connections.SendRequest',
name: 'Cancel',
payload: {
InSkillProduct: {
productId: "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
}
},
token: "correlationToken"
})
.getResponse();
This sample code uses the Alexa Skills Kit SDK for Node.js (v1).
this.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Cancel',
'payload': {
'InSkillProduct': {
'productId': 'amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
}
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
JSON syntax for a Connections.SendRequest
directive for a cancel or refund request. In this case, the name
is Cancel
.
{
"directives": [
{
"type": "Connections.SendRequest",
"name": "Cancel",
"payload": {
"InSkillProduct": {
"productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
},
"token": "correlationToken"
}
]
}
Directive fields
Field | Type | Description | Required |
---|---|---|---|
type |
String | The type of directive. AlwaysSendRequest for a purchase flow |
Yes |
name |
String | Indicates the target for the SendRequest message. Always Cancel for a refund or cancellation. |
Yes |
payload |
Object | Contains details for the specified action. For a cancel request, this includes the InSkillProduct property with a product ID. |
Yes |
token |
String | A token to identify this message exchange and store skill information. The token is not used by Alexa, but is returned in the resulting Connections.Response . You provide this token in a format that makes sense for the skill. |
Yes |
shouldEndSession |
Boolean | Send true to indicate that the session should end. However, the session will always end after you send a SendRequest message. |
Yes |
Manage the inventory for consumable purchases
If you offer consumable products, you need to maintain the user's inventory of items as they purchase and use the items. Alexa keeps track of the total number times the user purchased a particular product, but this does not reflect the user's consumption of the purchased units. When your skill gets an ACCEPTED
response for a consumable purchase, you need to:
- Update the user's inventory in persistent storage to reflect the correct number of items. For instance, add five items if the user bought a "five hint pack".
- Use the item if appropriate and decrement the inventory.
- Save the inventory updates in persistent storage.
The ASK SDKs for Node.js, Java, and Python all include a PersistenceAdapter
to save attributes. For details, see
ASK SDK documentation about persistent attributes.
When storing the user's inventory in persistent storage, always map the inventory to the userId
included in the request. The userId
is always provided in the context
object in the request (context.System.User.userId
). The default persistence layer provided in the SDKs uses userId
automatically.
For a sample skill that offers consumables and manages the inventory, see Name the Show.
Get the total purchases for a consumable product
To get the total number of times a user has purchased a particular consumable product, you can call the InSkillProducts
API, then check the activeEntitlementCount
property. The value for activeEntitlementCount
reflects the total purchases the user has made. Each purchase of the product increments activeEntitlementCount
by one. Each cancellation (such as due to a payment failure or refund request) decrements activeEntitlementCount
by one.
Note that total purchases may not correspond to the number items the user can use in your skill if you offer multi-item packs. For example, your skill offers a "five hint pack" for sale. A user purchases this pack three times. With each purchase, you increment their inventory by five. In this case:
- The
activeEntitlementCount
returned from the InSkillProducts API is3
- The inventory you maintain for the user contains 15
The user then uses 14 of the 15 hints. At this point:
- The
activeEntitlementCount
returned from the InSkillProducts API is still3
- The inventory you maintain for the user contains
1
Validate the inventory on every request
It is possible for the user's number of entitled consumable purchases to change outside of a normal skill session. For example:
- The user requested a refund for an accidental purchase. The next time the user opens your skill,
activeEntitlementCount
returns a smaller value than before. The user's inventory in your persistent storage reflects the user's pre-refund purchase. - During your own testing, you use the
reset-isp-entitlement
to clear your own purchases and start over. The next time you open your skill,activeEntitlementCount
returns 0, but your own inventory in persistent storage reflects your earlier test purchases.
To account for cases like these, check and update the inventory on every incoming request. The ASK SDKs for Node.js, Java, and Python all include request interceptors, which are a useful way to run code before your normal request handler. This can be a good way to implement this inventory check. For details, see ASK SDK documentation about request interceptors.
Maintain the user inventory if the user disables and re-enables the skill
If your skill offers consumable products, the user's userId
remains the same even if the user disables and re-enables the skill. This ensures that the inventory of items that you maintain is not lost in this scenario.
You do not need to do anything to get this special userId
behavior in this scenario. However, if you use the AlexaSkillEvent.SkillDisabled
event to perform any cleanup after a user disables your skill, be sure to retain the user's consumable purchase inventory. You can check the userInformationPersistenceStatus
property in the event object:
PERSISTED
: If the user re-enables the skill, they will get the sameuserId
, so do not clear their data.NOT_PERSISTED
: If the user re-enables the skill, they will get a newuserId
. Therefore, it is safe to clear data you have stored with theuserId
.
userId
behavior described here applies only to skills that offer consumable products. Alexa resets the userId
when a skill that does not offer consumable purchases is disabled.Sample code
- Name the Show – A sample skill that demonstrates selling consumable items by selling the user hints for a trivia game. Available in Node.js.
-
Premium fact skill – A sample skill that demonstrates how to use the in-skill purchase features of Alexa skills by offering different packs of facts behind a purchase, and a subscription to unlock all of the packs at once. Available in Node.js and Python:
- Premium "Hello World" skill – A sample skill that demonstrates how to use ISP features by offering a "Greetings Pack" and a "Premium Subscription". Available in Node.js and Java: