Editor’s Note: In a previous code deep dive series post, we provided an end-to-end walkthrough of how to implement in-skill purchasing (ISP) in an Alexa skill using Node.js. The deep dive references the Premium Hello World Skill, which is a sample skill that demonstrates how to use ISP features by offering a "Greetings Pack” and a “Premium Subscription" that greets the customer in a variety of languages in different accents using Amazon Polly. In today’s tutorial, we will walk through how to implement ISP in an Alexa skill using the Alexa Skills Kit (ASK) Software Development Kit (SDK) for Java using the Premium Hello World sample skill. The complete sample Premium Hello World sample skill is available on GitHub.
The Premium Hello World sample skill offers simple greetings in English. The skill offers the Greetings Pack as a one-time purchase as well as a subscription. This provides customers access to greetings in a variety of languages in different accents using Amazon Polly. For details on the various ISP scenarios in this skill, please refer to this code deep dive post.
The ASK SDK for Java provides the MonetizationServiceClient, which is used to get in-skill products. It can be obtained from the handler input as follows:
MonetizationServiceClient client = input.getServiceClientFactory().getMonetizationService();
This client allows us to obtain a single in-skill product, using the product ID:
InSkillProduct product = client.getInSkillProduct(locale, productId);
It also allows us to obtain a list of in-skill products:
InSkillProductsResponse response = client.getInSkillProducts(locale, null, null, null, null, null);
List<InSkillProduct> inSkillProducts = response.getInSkillProducts();
Using this list of in-skill products, we can obtain a list of entitled products and purchasable products. For example, we can get list of entitled products as shown here:
public static List<String> getAllEntitledProducts(List<InSkillProduct> inSkillProducts) {
return inSkillProducts.stream()
.filter(product -> product.getEntitled().toString().equalsIgnoreCase("ENTITLED"))
.map(product -> product.getName())
.collect(Collectors.toList());
}
Once we decide to offer an upsell to the customer, we will add a SendRequestDirective to the response:
if(shouldUpsell(input)) {
String upsellMessage = String.format("By the way, you can now get greetings in more languages. %s. %s", greetingsPackProduct.get().getSummary(), getRandomObject(SkillData.LEARN_MORE_STRINGS));
//Upsell Greetings Pack
return input.getResponseBuilder()
.addDirective(getUpsellDirective(greetingsPackProduct.get().getProductId(), upsellMessage))
.build();
}
The SendRequestDirective for upsell can be built using the upsell message, productid and token as shown below. We set the name for the SendRequestDirective as upsell using - .withName("Upsell")
public static SendRequestDirective getUpsellDirective(String productId, String upsellMessage) {
// Prepare the directive payload
Map<String,Object> mapObject = new HashMap<>();
Map<String, Object> inskillProduct = new HashMap<>();
inskillProduct.put("productId", productId);
mapObject.put("upsellMessage", upsellMessage);
mapObject.put("InSkillProduct", inskillProduct);
return SendRequestDirective.builder()
.withPayload(mapObject)
.withName("Upsell")
.withToken("correlationToken")
.build();
}
Similarly, the SendRequestDirective for when the customer wants to buy an ISP or cancel the purchase by passing the type - “Buy”, “Cancel,” etc.
public static SendRequestDirective getDirectiveByType(String productId, String type) {
// Prepare the directive payload
Map<String, Object> payload = new HashMap<>();
Map<String, Object> inskillProduct = new HashMap<>();
inskillProduct.put("productId", productId);
payload.put("InSkillProduct", inskillProduct);
// Prepare the directive request
SendRequestDirective directive = SendRequestDirective.builder()
.withPayload(payload)
.withName(type)
.withToken("correlationToken")
.build();
return directive;
}
When a customer says “yes” to buying a pack, Alexa’s Purchase Experience Flow takes over and responds back to the customer with more details about the product, along with the pricing information (provided when the product is created).
When the customer accepts the upsell offer by responding with a “Yes” to “Would you like to buy it?” Alexa responds back with a Connections.Response, which includes the purchaseResult property and indicates the result of the purchase transaction: ACCEPTED, DECLINED, ALREADY_PURCHASED, or ERROR.
After the purchase is completed, Alexa sends a Connections.Response directive back to our skill with a purchaseResult of “ACCEPTED.” To handle the Connections.Response, we will implement the BuyResponseHandler. In the canHandle() method of this handler, we can check the request name as shown below:
public boolean canHandle(HandlerInput input, ConnectionsResponse connectionsResponse) {
String name = input.getRequestEnvelopeJson().get("request").get("name").asText();
return (name.equalsIgnoreCase("Buy") || name.equalsIgnoreCase("Upsell"));
}
We will check if the status code for this request was “200 OK.”
String code = input.getRequestEnvelopeJson().get("request").get("status").get("code").asText();
If the status code is 200, then we can handle the various scenarios of the purchaseResult (ACCEPTED, DECLINED, ALREADY_PURCHASED, or ERROR) as shown below:
if (inSkillProduct.isPresent() && code.equalsIgnoreCase(SUCCESS_CODE)) {
String preSpeechText;
final String purchaseResult = handlerInput.getRequestEnvelopeJson().get("request").get("payload")
.get("purchaseResult").asText();
switch (purchaseResult) {
case "ACCEPTED": {
preSpeechText = IspUtil.getBuyResponseText(inSkillProduct.get().getReferenceName(), inSkillProduct.get().getName());
break;
}
case "DECLINED": {
preSpeechText = "No Problem.";
break;
}
case "ALREADY_PURCHASED": {
preSpeechText = IspUtil.getBuyResponseText(inSkillProduct.get().getReferenceName(), inSkillProduct.get().getName());
break;
}
default:
preSpeechText = String.format("Something unexpected happened, but thanks for your interest in the %s.", inSkillProduct.get().getName());
}
return IspUtil.getResponseBasedOnAccessType(handlerInput, inSkillProducts, preSpeechText);
}
//Something failed
System.out.println(String.format("Connections.Response indicated failure. error: %s", handlerInput.getRequestEnvelopeJson().get("request").get("status")
.get("message").toString()));
return handlerInput.getResponseBuilder()
.withSpeech("There was an error handling your purchase request. Please try again or contact us for help.")
.build();
In the case of ACCEPTED, we inform the customer that they have purchased the in-skill product and provide them with a premium greeting. In case of DECLINED, we offer them the simple greeting. In case of ALREADY_PURCHASED, we offer them the premium greeting.
You can find the complete implementation of this skill in Java on GitHub.