Add In-Skill Purchases to a Custom Skill
When you implement in-skill purchases 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
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
- Additional Resources
Prerequisites
In order to complete the steps in this document you should have:
- A skill with a custom interaction model, and associated Lambda function. See Steps to Build a Custom Skill for more information. 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 a Lambda function.
- A skill with at least one product added to your custom skill. For more information see Use the ASK CLI to Manage In-Skill Purchases
Get the in-skill products list
To offer in-skill purchases 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 that are marked ENTITLED)
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 for the duration of the skill session.
If the request is successful, the message result will contain an InSkillProducts object that contains a list of InSkillProduct objects.
Example
The following code shows how you might add a call to the InSkillProductsAPI in skill Lambda function. This code contains a function that 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 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]));
}
}
Then call the function from your launch request handler:
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
...
getProductsAndEntitlements(this, functionToProcessProductListOnceItIsLoaded);
...
}
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 a custom intent to support a user request to buy a product by name. The sample intent lists variants to buy an item such as buy, purchase, want, would like, and a custom slot that contains a list of the in-skill products.
Example
{
"name": "BuySkillItemIntent",
"samples": [
"buy",
"shop",
"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:
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 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.
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.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Buy',
'payload': {
'InSkillProduct': {
'productId': 'your product id in the format amzn1.adg.product....'
}
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
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 this request, always an InSkillProduct that contains 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
Amazon provides the purchasePromptDescription and 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
SendRequestcall.
For the upsellMessage:
- Never include price or offer details. Be careful not to reference the 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 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.
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.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Upsell',
'payload': {
'InSkillProduct': {
'productId': 'your product id in the format amzn1.adg.'
},
'upsellMessage': 'product suggestion'
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
Directive Fields
| Field | Type | Description | Required |
|---|---|---|---|
type |
String | The type of directive. Always SendRequest 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 this request, always an InSkillProduct that contains a product ID. |
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.Responsemessage
Handle the result
The result of the purchase flow is a Connections.Response directive that contains 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 code shows a Connections.Response directive 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"
}
Directive 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. name specifies Upsell for a purchase suggestion, or Buy for a buy request, 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:
"Great. You now have in-skill product name"
What your skill should do:
Automatically start requested content, for example, "Let's play it now…" If the user commits to a purchase, make sure you complete their original request.
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: "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
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
{
"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.
The following example shows how you might add code to send a Connections.SendRequest directive for a refund request.
this.handler.response = {
'version': '1.0',
'response': {
'directives': [
{
'type': 'Connections.SendRequest',
'name': 'Cancel',
'payload': {
'InSkillProduct': {
'productId': 'your product id in the format amzn1.adg.product....'
}
},
'token': 'correlationToken'
}
],
'shouldEndSession': true
}
};
this.emit(":responseReady");
Directive fields
| Field | Type | Description | Required |
|---|---|---|---|
type |
String | The type of directive. Always SendRequest for a purchase/cancel flow |
Yes |
name |
String | Indicates the target for the SendRequest message. Always 'name': 'Cancel' for a refund or cancellation. |
Yes |
payload |
Object | Contains details for the specified action. For this request, always an InSkillProduct that contains 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 |