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:

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 marked ENTITLED.
  • 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

Copied to clipboard.

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   

Copied to clipboard.

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.

Example

The following example shows how you might add code to a Lambda function to send the Connections.SendRequest directive for a buy request.

Copied to clipboard.

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();

Copied to clipboard.

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");

Copied to clipboard.

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.

Example

The following example shows how you might add code to a Lambda function to send the Connections.SendRequest directive for a purchase suggestion.

Copied to clipboard.

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();

Copied to clipboard.

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");

Copied to clipboard.

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

Type of request.
Always set to Connections.Response.

String

requestId

Unique identifier for the Connections.Response request.

String

timeStamp

Time of the Connections.Response request.

String

name

Name of the request to which this response applies.
Set to Upsell, Buy, or Cancel (for cancellation or refund).

String

status

HTTP status result.

Object

status.code

HTTP response status such as 200, 400, 404.

String

status.message

HTTP status message, such as OK, Not Found, Not Authorized.

String

payload

Contains the results of the purchase or cancel transaction.

Object

payload.purchaseResult

Result of the purchase or cancel request.
Valid values: ACCEPTED, PENDING_PURCHASE, DECLINED, ALREADY_PURCHASED, ERROR.

String

payload.productId

In-skill product ID sent in the request.

String

payload.message

(Optional) Additional details about the cancel or purchase transaction.

String

token

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.

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.

The following example shows how you might add code to send a Connections.SendRequest directive for a refund request.

Copied to clipboard.

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();

Copied to clipboard.

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");

Copied to clipboard.

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 is 3
  • 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 still 3
  • 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 same userId, so do not clear their data.
  • NOT_PERSISTED: If the user re-enables the skill, they will get a new userId. Therefore, it is safe to clear data you have stored with the userId.

Sample code

The following sample code demonstrates how to use in-skill purchasing with products with different payment models:


Was this page helpful?

Last updated: Nov 23, 2023