Vielen Dank für Ihren Besuch. Diese Seite ist momentan nur auf Englisch verfügbar. Wir arbeiten an der deutschen Version. Vielen Dank für Ihr Verständnis.

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

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]));
    }
}
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);
    ...
}      
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:

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");
return handlerInput.responseBuilder
        .addDirective({
            type: "Connections.SendRequest",
            name: "Buy",
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.3b98cb42-bbd8-4c64-afba-5ba24501cfd2",
                }
            },
            token: "correlationToken",
        })
        .getResponse();

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
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 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': {
                            'amzn1.adg.product.3b98cb42-bbd8-4c64-afba-5ba24501cfd2'                          
                         },
                        'upsellMessage': 'This is my product.  Do you want to know more?'      
               },
              'token': 'correlationToken'   
          }
      ],
      'shouldEndSession': true
  }
};

this.emit(":responseReady");
return handlerInput.responseBuilder
        .addDirective({
            type: "Connections.SendRequest",
            name: "Upsell",
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.3b98cb42-bbd8-4c64-afba-5ba24501cfd2",
                },
                upsellMessage: "This is my product.  Do you want to know more?",
            },
            token: "correlationToken",
        })
        .getResponse();

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.Response message

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.

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");
return handlerInput.responseBuilder
        .addDirective({
            type: 'Connections.SendRequest',
            name: 'Cancel',
            payload: {
                InSkillProduct: {
                    productId: "amzn1.adg.product.3b98cb42-bbd8-4c64-afba-5ba24501cfd2",
                }
            },
            token: "correlationToken"
        })
        .getResponse();

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

Additional Resources