Since Amazon announced the ability to offer in-skill purchases for developers creating Alexa skills, I’ve added four pieces of premium content to two different skills. I recently published an article about why you should consider adding premium content, and in this article, I follow up with step-by-step code samples to show you exactly how you can do that via the ASK SDK.
Before getting into the code, I want to review the types of in-skill products (or ISPs) that Amazon has made available for Alexa, the different ways you can surface them, and some considerations to keep in mind about when and how often you prompt your customer with reminders about your content.
There are three types of ISPs that your skill can offer:
Once you’ve decided which type of product you want to offer, you should think about how you are going to message that product to your customers. You can do this on the home page description for your skill, but since many customers don’t read that content thoroughly, you’re going to need to make them aware of the products you offer via your voice content. The best way to make users aware of this content and to increase your conversion rate is to do it during a natural part of the conversation, where the premium content might be useful:
Selecting a new machine in Slot Machine
For the coding sample, I am going to be referring to one of the premium games that I offer for purchase in my Slot Machine skill. Slot Machine allows customers to play multiple machines, and they can say “choose a different game” to switch from one machine to another. My premium content can be presented to the user in one of three places — when they first launch the skill, when they switch to a different machine, and after continuously playing the same machine for a given amount of time.
In order to add an in-skill product to your Alexa skill, you have to use the ASK CLI. If you haven’t used the CLI, follow steps 1–3 from these instructions to set it up. Once you have it set up, you’ll want to clone the skill to which you want to add a premium product. Do this with the clone command
ask clone
This will provide you with a list of your Alexa skills. Use the arrow keys to select the skill you want to clone and hit enter. Cloning will copy the skill manifest, metadata, and code if you are using a Lambda function to your machine. After you have this local copy, change to the root directory of the newly cloned skill and use this command to set up your in-skill product.
ask add isp
This will present you with a list of product types you can add
List of in-skill product types you can choose (Use arrow keys)> ConsumableEntitlementSubscription
Select the one that you’d like to use, and accept the default template that you’re presented with. After doing this, you’ll be prompted to type in the name of your new in-skill product. Suppose you call it extragame
. This will create a new file called extragame.json
in an isp folder in your skill.
Open this file, and you’ll see several details that you need to fill out to describe your skill. Specifically, you’ll need to provide a name, description, icons (in 108x108 and 512x512 format), and some release details that describe the pricing and release date for your product offering. It’s also worth noting that you will need to provide a link to a privacy policy for your skill. Not to worry, you can find several examples of policies you can leverage by searching online for “Alexa skill privacy policy.”
Once you’ve filled in this information, you can deploy your skill using ask deploy
which will upload the new product settings for your skill.
Now that you’ve uploaded a product setting, your need to add support in your code. The first thing you should do is use the MonetizationServiceClient
to check which products are available when the user launches your skill. The following code shows how you can do this and save the availability of each product within your session attributes:
const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
return ms.getInSkillProducts(event.request.locale).then((inSkillProductInfo) => {let state;attributes.paid = {};
if (inSkillProductInfo) {inSkillProductInfo.inSkillProducts.forEach((product) => {if (product.entitled === 'ENTITLED') {state = 'PURCHASED';} else if (product.purchasable == 'PURCHASABLE') {state = 'AVAILABLE';}
if (state) {
attributes.paid\[product.referenceName\] = {
productId: product.productId,
state: state,
};
}
}
});}).catch((error) => {// Ignore errors});
Now within your attributes.paid
object, you have a list of all products, with a state set to either PURCHASED
or AVAILABLE
. You can check against this object when you attempt to sell a product offer to see whether they have already purchased it, or whether it is available for them to purchase.
The code to offer a product for purchase can be a little tricky. Basically, you are going to hand-off the purchase request to Alexa, exiting your user session in the process. Alexa will handle the interaction and payment flow with the customer, including offering any discounts that Amazon may be running at the time. Your code will then be called back with a new session and the result of whether the customer purchased your product or not.
TheConnections.SendRequest
response to Alexa kicks off this process. Fortunately, you can pass a token as part of this response which will be returned to you as part of the new session request. I’ve found this to be handy and use this to let me know both which product I was offering (remember, I support multiple products), and which handler sent me the request (since I can make this upsell offer in multiple locations). This allows me to seamlessly drop the user back into the interaction flow. If they declined a purchase response, I know what they were attempting to do before I interrupted them with an offer, so I can drop them right back where they were. If they accepted the purchase, I also have the context so can drop them in the appropriate part of the code. For example, if the user is being sold a holiday-themed slot machine during the process of selecting a new game, I'd use a token namedholiday.select
Here’s what that code looks like:
// Create an upsell directiveconst directive = {'type': ' Connections.SendRequest’,‘name’: ‘Upsell’,‘payload’: {‘InSkillProduct’: {productId: attributes.paid[upsellProduct].productId,},‘upsellMessage’: 'We have a holiday themed game available for purchase, want to hear more?',},‘token’: 'holiday.select',};
return handlerInput.responseBuilder.addDirective(directive).withShouldEndSession(true).getResponse();
Note that the upsellMessage
field needs to be in plaintext. Any SSML tags that you set for different voices or speech variation will be ignored when read by Alexa. From a customer interaction standpoint, what they will hear is something like this — note that after the user says yes to the upsellMessage set by your code, the interaction is handled directly by Alexa:
Alexa, select a new machineWe have a holiday themed game available for purchase, want to hear more?YesThis will add the Holiday Game to the list of slot machines you can play. Prime members save $0.19. Without Prime, your price is $0.99 plus tax. Would you like to buy it?YesPlease say your 4-digit code to confirm the purchase.
If you are handling an explicit purchase as opposed to an upsell (i.e. responding to a request like “Alexa, buy the holiday game”), then you set name
in the directive to ‘Buy’ and remove the upsellMessage
field. You’ll still want to set the token so you can handle the response when they return to your skill.
And don’t forget the cancellation message! Again, like the purchase case, you’ll need to support an utterance that allows the customer to refund their purchase (“Alexa, refund the holiday game”). For this message, you set name
in the directive to ‘Cancel’ and again remove the upsellMessage
field.
Whether the user says yes or no to your upsell request, or if there’s some other error during the purchasing flow, your skill will be invoked with a new session with a request type of Connections.Response
You can then take the user to whatever the natural continuation point for them is. If they hear this message while selecting a new machine and make the purchase, I drop them into that machine selected and ready to play. If they decline the purchase, I tell them what other machines we have available so they can continue the process of selecting a new machine. If there was some sort of error during purchase, I put them into the same flow as if they declined the purchase. Alexa will handle telling them there was an error, so no need for our code to repeat that. The handler for the response looks like this:
canHandle: function(handlerInput) {const request = handlerInput.requestEnvelope.request;return (request.type === 'Connections.Response');},handle: function(handlerInput) {const event = handlerInput.requestEnvelope;const attributes = handlerInput.attributesManager.getSessionAttributes();
// options[0] will be the name of the game we were selling// options[1] will be the intent they were sold inconst options = event.request.token.split('.');const accepted = (event.request.payload &&((event.request.payload.purchaseResult == 'ACCEPTED') ||(event.request.payload.purchaseResult == 'ALREADY_PURCHASED')));let nextAction = options[1];
if ((event.request.name === 'Upsell') && !accepted) {// Don't upsell them again on the next roundattributes.noUpsell = true;}
// Did they accept?if (accepted) {// If this is was a cancellation remove itif (event.request.name === 'Cancel') {attributes[options[0]] = undefined;if (attributes.paid && attributes.paid[options[0]]) {attributes.paid[options[0]].state = 'AVAILABLE';}} else {// We'll auto-select// Make sure we put it into the list of paid products as purchasedif (attributes.paid && attributes.paid[options[0]]) {attributes.paid[options[0]].state = 'PURCHASED';}nextAction = 'autoselect';}}
// And go to the appropriate next step// Select, SelectYes, Spin, and Launch are intent handlersif (nextAction === 'select') {return Select.handle(handlerInput);} else if (nextAction === 'autoselect') {attributes.gameToSelect = options[0];return SelectYes.handle(handlerInput);} else if (nextAction === 'spin') {return Spin.handle(handlerInput);} else {// Just drop them directly into a gamereturn Launch.handle(handlerInput);}
You’ll notice that in addition to setting up the new machine if selected and handing off to the appropriate next intent for processing, there is code that sets a noUpsell
flag if the user was given an upsell message and declined it. This is to avoid an infinite loop, since I drop the user back into the same path they were previously in. Before presenting an upsell message, my code checks whether this flag is set to avoid an infinite loop. This also helps ensure that customers are only heard an upsell message once during their session, giving them a better customer experience.
I hope you found this guide useful, and good luck adding premium content to your own Alexa skills!