Add ISP Support to Your Skill Code
When you add In-Skill Purchasing (ISP) to your custom skill, you add purchase flows to your skill logic to transfer control to Alexa to guide the customer through the purchase.
Complete the following steps to manage your product inventory, invoke the purchase flow, and resume your skill after the purchase flow completes. For consumable products, save the customer's inventory of items in persistent storage and manage the inventory as the customer uses the items.
Prerequisites
Before you add in-skill purchasing to your skill, you must meet the following prerequisites:
- A skill with a custom interaction model, and associated Lambda function. For details, see Steps to Build a Custom Skill. 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 flow. For details, see Design a Good Customer Experience for In-Skill Purchasing.
Get the in-skill products list
At the start of each skill session, get the list of in-skill products that are available to the customer. Use the inventory of products to determine the customer's paid products and the products to offer for sale as follows:
- Know the product ID for each product. You map user requests for products to product IDs.
- Know what products are offered for sale to this customer.
Look for products marked
PURCHASABLE
. - Know what this user has already purchased.
Look for products markedENTITLED
. - For consumable products, know the number of units of the product this user has purchased as of today. Check the
activeEntitlementCount
.
To get the list of available in-skill products, use the List in-skill products API. 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 contains a list of products that match your filter criteria.
Example
This code example 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 code example 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 add support for a customer to shop products and buy one by name in your skill. For example, you might 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 customer 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 implement the buy intent, your code must handle the following two scenarios.
The customer doesn't specify a product
If the customer asks to shop the products you offer, direct the customer to specify one product. In other words, your code detects a generic shop or buy request and responds to the customer with a list of eligible products to choose from. When you give a list:
- Make options easier to remember by listing only two or three at a time
- Use periods or break SSML tags instead of commas to clearly distinguish one option from another.
For example:
User: What can I buy?
Alexa: I have two expansion packs available. Cave Quest or Deep Sea. Which are you interested in?
The customer specifies a product
The customer might specify a product directly, or you might direct them to choose a product. Regardless, after the customer specifies the product to buy, you start 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 purchaseand obtains the product description and list price from the product schema. When the purchase completes, your skill re-launches with a new session and with a purchase result supplied to your skill.
Buy
request. Make sure that you save any relevant user data in a persistent data store. After the purchase flow completes, Alexa launches your skill again. You can use the stored data to continue the skill. For details about persistent attributes, see
ASK SDK documentation for Node.js, Java, or Python.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 code example 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 code example 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 customers to buy your in-skill products is with a purchase suggestion, which means you proactively offer products related to how the customer is currently interacting with your skill. You would check whether a customer 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 and includes the upsell message.
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 customer intent with a purchase suggestion, you send a Connections.SendRequest
directive to Alexa that:
- Indicates a purchase suggestion by specifying the
Upsell
task - 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. Alexa passes the token back to your skill in the response from the
SendRequest
call.
For more guidance about what to include in the upsell message, see Design the purchase suggestion.
Your skill session ends when the purchase flow starts. Amazon handles the voice interaction model and all the mechanics of the purchase. Alexa plays the upsell message, and when the customer confirms, plays the product description and list price from the product schema. When the purchase completes, Alexa relaunches your skill, and supplies a purchase result.
Upsell
request. Make sure that you save any relevant user data in a persistent data store. After the purchase flow completes, Alexa launches your skill again. You can use the stored data to continue the skill. For details about persistent attributes, see
ASK SDK documentation for Node.js, Java, or Python.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 code example 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 code example 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
After the purchase flow completes, Alexa launches your skill again. Add code to handle the purchase result and continue with the skill seamlessly.
Your skill receives the result of the purchase request as a Connections.Response
request. Resume the skill based on the purchaseResult
included in the payload. Alexa also returns the token
that you passed in the request. You can use the token to help you resume the skill where the customer left off.
Use the guidelines for details about how to handle each result type.
Example purchase result
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"
}
Purchase result definition
Property | Description | Type |
---|---|---|
|
Type of request. |
String |
|
Unique identifier for the Connections.Response request. |
String |
|
Time of the Connections.Response request. |
String |
|
Name of the request to which this response applies. |
String |
|
HTTP status result. |
Object |
|
HTTP response status such as |
String |
|
HTTP status message, such as |
String |
|
Contains the results of the purchase or cancel transaction. |
Object |
|
Result of the purchase or cancel request. |
String |
|
In-skill product ID sent in the request. |
String |
|
(Optional) Additional details about the cancel or purchase transaction. |
String |
|
Optional string that Alexa returns in the response when the purchase or cancel flow completes. For example, you might use the token to store information to help you resume the skill after the purchase. |
String |
Guidelines to handle the purchase result
Check the status of the product returned in the response by using the Get all in-skill products API for payload.productId
. 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 customer commits to a purchase, make sure you complete their original request.
For a consumable product, your skill also needs to update the customer'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
PENDING_PURCHASE result
At times the purchase flow might take longer than expected. For example, due to local banking regulations, the bank might require the customer to complete payment authentication outside of the Alexa purchase flow. To let the skill know that the purchase is in progress, Alexa returns PENDING_PURCHASE
.
What your skill should do:
- Check the customer's entitlement for the purchase. If the response indicates that the customer paid for the product (
entitled
=ENTITLED
), continue with premium content. - Otherwise, pick up where the customer left off and offer a way for the customer to continue. For example, continue with other purchased content or free content.
- Don't mention that the purchase is pending.
- At the start of the next skill session, make sure that you check the customer's entitlement because the purchase might complete outside the skill session.
DECLINED result
What Alexa says:
- One time purchase or consumable: "OK"
- 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 customer left off and offer a way for the customer 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 customer left off or offer a way for them to continue.
- Don't mention that an error occurred. The customer receives an explanation from the purchase flow before they return to your skill.
- Don't ask the customer if they'd like to try again.
Example:
User agrees to the purchase, but needs to update payment information
Alexa: Hmm, I had a problem with your payment method. To update, check out 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 is possible for one-time purchases and subscriptions.
This result isn't returned for consumables because customers can purchase consumable products multiple times.
What Alexa says:
Good news, you already have in-skill product name
What your skill should do:
- Automatically start the requested content.
- Don't 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 customer request to refund a purchase and forward the request to the purchase flow.
A customer 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 customer 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
request. Make sure that you save any relevant user data in a persistent data store. After the purchase flow completes, Alexa launches your skill again. You can use the stored data to continue the skill. For details about persistent attributes, see
ASK SDK documentation for Node.js, Java, or Python.The following example shows how you might add code to send a Connections.SendRequest
directive for a refund request.
This code example 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 code example 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 customer's inventory of items as they purchase and use the items. Alexa keeps track of the total number times the customer purchased a particular product, but this number doesn't reflect the customer's consumption of the purchased units. When your skill gets an ACCEPTED
response for a consumable purchase, you need to:
- Update the customer's inventory in persistent storage to reflect the correct number of items. For example, if the customer buys a "five hint pack," add five items to the hint inventory.
- 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 you store the customer's inventory in persistent storage, always map the inventory to the userId
included in the request. The userId
is provided in the context
object in the request (context.System.User.userId
). The default persistence layer provided in the SDKs uses userId
automatically.
Get the total purchases for a consumable product
To get the total number of times a customer has purchased a particular consumable product, you can call the
Get customer entitlement API, and then check the activeEntitlementCount
property in the response. The value for activeEntitlementCount
reflects the total purchases the customer 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.
If you offer multi-item packs, the total purchases might not correspond to the number of items that the customer can use in your skill. For example, your skill offers a "five hint pack" for sale. A customer purchases this pack three times. With each purchase, you increment their inventory by five. In this example, the following statements are true:
- The
activeEntitlementCount
returned from the Get customer entitlement is3
- The inventory you maintain for the customer contains 15
The customer then uses 14 of the 15 hints. At this point:
- The
activeEntitlementCount
returned from the Get customer entitlement is still3
- The inventory you maintain for the customer contains
1
Validate the inventory on every request
It's possible for the customer's number of entitled consumable purchases to change outside of a normal skill session. For example:
- The purchase flow took longer than expected, but succeeded after the skill session ended. The next time the customer opens your skill,
activeEntitlementCount
returns a larger value than the customer's inventory reflects. - The customer requested a refund for an accidental purchase. The next time the customer opens your skill,
activeEntitlementCount
returns a smaller value than before. The customer's inventory in your persistent storage reflects the customer'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 include request interceptors. Use these to run code before your normal request handler. Implement the customer entitlement check here. For more details, see request interceptors..
Maintain the customer inventory if the customer disables and re-enables the skill
If your skill offers consumable products, the userId
remains the same even if the customer disables and re-enables the skill. This ensures that the inventory of items that you maintain isn't lost in this scenario.
You don't 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 customer disables your skill, be sure to retain the customer'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
The following sample code demonstrates how to use in-skill purchasing with products with different payment models:
- Build An Alexa Skill with In-Skill Purchases - Premium Fact (Node.js)
- Build An Alexa Skill with In-Skill Purchases using ASK Python SDK
- Build An Alexa Skill with In-Skill Purchases - Premium Hello World (Node.js)
- Build An Alexa Skill with In-Skill Purchases - Premium Hello World (Python)
Related topics
- Make Money with Alexa Skills: Adding In-Skill Purchasing
- Code Deep Dive: Implementing In-Skill Purchasing for Subscriptions with Node.js
- Testing Guide
- Certification Guide
Last updated: Mar 26, 2024