スキルコードにスキル内課金の機能を追加する

スキルコードにスキル内課金の機能を追加する

スキル内課金(ISP)をカスタムスキルに追加すると、購入フローをAlexaへの引き渡しとしてスキルロジックに追加し、ユーザーに購入手順を案内できます。

以下の手順を実行して、商品のインベントリー管理、購入フローの呼び出し、購入フロー完了後のスキルの再開を行います。消費型商品の場合、ユーザーのアイテムのインベントリーを永続ストレージに保存し、ユーザーがアイテムを使用するごとにインベントリーを管理します。

前提条件

スキル内課金を追加する前に、次の前提条件を満たす必要があります。

スキル内商品のリストを取得する

各スキルセッションの開始時に、ユーザーが購入可能なスキル内商品のリストを取得します。商品のインベントリーを使って、ユーザーが購入済みの商品と提供する商品を次のように判断します。

  • 各商品の商品IDを調べます。商品に対するユーザーリクエストを商品IDにマッピングします。
  • このユーザーに提供されている商品を調べます。PURCHASABLEとマークされた商品を探します。
  • このユーザーが既に購入済みの商品を調べます。
    ENTITLEDとマークされた商品を探します。
  • 消費型商品の場合は、このユーザーが今日までに購入済みの商品のユニット数を調べます。activeEntitlementCountを確認します。

リストは、スキル内商品サービスのInSkillProducts APIに対するHTTP GETリクエストを使用して取得します。この呼び出しを起動リクエストハンドラーに追加し、スキルセッション中はこの商品のリストを保存します。リクエストが成功すると、InSkillProductオブジェクトのリストを含むInSkillProductsオブジェクトがメッセージ結果に含まれます。

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。

このLaunchRequestハンドラーは、SDKで提供されているMonetizationServiceClientを使用して、InSkillProducts APIを呼び出し、商品のリストを取得します。ウェルカムメッセージで、既に購入済みの商品がユーザーに示されます。

/*
    この関数はinSkillProductリストをフィルタリングしてすべての購入済み商品の
    リストを取得し、それに従ってSkill CXをレンダリングする方法を示します
*/
function getAllEntitledProducts(inSkillProductList) {
  const entitledProductList = inSkillProductList.filter(record => record.entitled === 'ENTITLED');
  return entitledProductList;
}

/*
    買い切り型商品のリストから、商品名の発話可能リストを返すヘルパー関数
    です。
*/
function getSpeakableListOfProducts(entitleProductsList) {
  const productNameList = entitleProductsList.map(item => item.name);
  let productListSpeech = productNameList.join(', '); // 商品名をカンマで区切った文字列を1つ生成します
  productListSpeech = productListSpeech.replace(/_([^_]*)$/, 'and $1'); // 最後のカンマを'と'に置き換えます
  return productListSpeech;
}

/*
  Request ハンドラーです。このハンドラーは、ユーザーが特定のインテントを指定せずに
  スキルを開始した場合に使用されます。
*/
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) {
          // ユーザーが商品を1つ以上所持している

          return handlerInput.responseBuilder
            .speak(`${skillName}へようこそ。現在${getSpeakableListOfProducts(entitledProducts)}` +
              '個の商品を所持しています。ランダムに豆知識を聞く場合は、\'豆知識を教えて\'と言ってください。' +
              '購入済みのカテゴリーを指定するには、たとえば\'科学の豆知識を教えて\'と言ってください。' +
              'ほかに購入できるものを知りたい場合は、\'買えるものを教えて\'と言ってください。それでは何を' +
              'しましょうか?')
            .reprompt('すみません、わかりませんでした。何をしましょうか?')
            .getResponse();
        }

        // これまで購入した商品がない場合。
        console.log('No entitledProducts');
        return handlerInput.responseBuilder
          .speak(`${skillName}へようこそ。ランダムに豆知識を聞く場合は、'豆知識を教えて'と言ってください。' +
            '購入できるプレミアムカテゴリーを知りたい場合は、\'買えるものを教えて\'と言ってください。' +
            'ヘルプが欲しい場合は、\'使い方を教えて\'と言ってください...それでは何をしましょうか?')
          .reprompt('すみません、わかりませんでした。何をしましょうか?')
          .getResponse();
      },
      function reportPurchasedProductsError(err) {
        console.log(`Error calling InSkillProducts API: ${err}`);

        return handlerInput.responseBuilder
          .speak('購入履歴のロード中に問題が発生しました。')
          .getResponse();
      },
    );
  },
}; // LaunchRequestHandlerを終了します   

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Node.js(v1)を使用しています。

このLaunchRequestハンドラーはHTTP GETリクエストを構築し、InSkillProducts APIを呼び出して商品のリストを取得します。getProductsAndEntitlements関数はコールバック関数のパラメーターを取り、非同期にHTTP呼び出しを行ってから、コールバック関数を呼び出します。HTTP呼び出しが完了すると、コールバック関数によって結果が処理されます。


function onLaunch(launchRequest, session, callback) {
    console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
    ...
    // 利用可能な商品を取得する関数を呼び出します
    getProductsAndEntitlements(this, functionToProcessProductListOnceItIsLoaded);
    ...    
}

function getProductsAndEntitlements(self, callback)   {
    // 購入APIを呼び出し、商品がまだキャッシュされていない場合にのみ商品をロードします
    if (!self.attributes.areProductsLoaded)    {
        self.attributes.inSkillProducts = [];
        var returnData = [];

        // APIを呼び出すために必要な情報がこのセッションで利用可能です
        const https = require('https');
        const apiEndpoint = "api.amazonalexa.com";
        const token  = "bearer " + self.event.context.System.apiAccessToken;
        const language    = self.event.request.locale;

        // APIパス
        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
            }
        };

        // 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", "購入履歴のロード中に問題が発生しました。エラーコードは" + 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", "製品リストのロード中に問題が発生しました。エラーコードは" + e.code + "、メッセージは" + e.message);
        });

    } // (!self.attributes.areProductsLoaded) {}であれば終了
    else    {
        console.log("製品情報は既にロードされています。");
        callback(self, self.attributes.InSkillProducts);
        return;
    }
}

// 商品リストを処理します。
var functionToProcessProductListOnceItIsLoaded = function(self, inSkillProductList)  {
    if (!inSkillProductList)    {
        console.log("製品リストのロード中に問題が発生しました。");
    }
    // 取得した商品リストに対するアクション
    for (var idx = 0; idx < inSkillProductList.length; idx ++)   {
        console.log("inSkillProductList[" + idx + "] is:" + JSON.stringify(inSkillProductList[idx]));
    }
}

購入リクエストに対応するコードを追加する

ユーザーがスキル内で商品を参照し、名前を指定していずれかを購入するためのコードを追加します。たとえば、次のようなリクエストをサポートできます。

  • 何を買えるの?
  • どんなものを買えるの?
  • 買えるものを教えて
  • <商品名>を買いたい
  • <商品名>が欲しい
  • アレクサ、<スキル呼び出し名>に<商品名>を頼んで

購入リクエストに対応するには、次を実行します。

  • 購入リクエストに対応するカスタムインテントを作成します
  • カスタムインテントを処理するコードを追加し、ディレクティブを送信して購入フローを開始します

インテントを作成する

以下は、商品を名前指定で購入するか、購入する商品を見つけるユーザーリクエストをサポートするためのカスタムインテントをモデリングするJSONの例です。

{
  "intents": [
    {
      "name": "WhatCanIBuyIntent",
      "samples": [
        "何を買えるの",
        "どんなものを買えるの",
        "買えるものを教えて",
        "買う",
        "買い物",
        "購入する"        
      ],
      "slots": []
    },    
    {
      "name": "BuyIntent",
      "samples": [
        "{ProductName}を買います",
        "{ProductName}を購入",
        "{ProductName}が欲しい",
        "{ProductName}をお願い"
      ],
      "slots": [
        {
          "name": "ProductName",
          "type": "LIST_OF_PRODUCT_NAMES"
        }
      ]
    }    
  ],
  "types": [
    {
      "name": "LIST_OF_PRODUCT_NAMES",
      "values": [
        {
          "id": "reference_name",
          "name": {
            "value": "商品A",
            "synonyms": [
              "Aという商品"
            ]
          }
        }
      ]
    }
  ]
}

インテントを処理する

購入インテントを処理する場合、コードは以下の2つのシナリオを処理する必要があります。

ユーザーが商品を指定しなかった場合

提供されている商品をユーザーがたずねてきた場合、ユーザーに商品のいずれかを指定するよう案内します。つまり、コードでは一般的な買い物または購入リクエストを検出し、ユーザーが選択して購入できる商品のリストを提供して応答を返す必要があります。リストを提供する場合は次のようにします。

  • 同時にリストとして提供する数を2~3個にして選択肢を覚えやすくします
  • 読点ではなく句点やSSMLのブレークタグを使用して、選択肢を区別しやすくします。

ユーザー: 何を買えるの?

Alexa: 利用できる拡張パックが2つあります。洞窟探検。または深海探査です。どちらにしますか?

ユーザーが商品を指定した場合

ユーザーが商品を直接指定する場合もあれば、商品を選択するようにこちらから指示する場合もあります。いずれにしても、ユーザーが購入する商品を指定したら、Buyディレクティブを送信して購入フローを開始します。

購入フローが開始されると、スキルセッションは終了します。音声対話モデルと、すべての購入メカニズムはAmazon側で処理されます。また、商品説明と標準価格を商品のスキーマから取得します。プライム会員の場合は、メッセージと価格は自動的に調整されます。購入が完了するとスキルが新しいセッションで再度起動し、購入結果がスキルに提供されます。

Lambda関数にコードを追加して購入リクエストのConnections.SendRequestディレクティブを送信する方法の例を以下に示します。

クリップボードにコピーされました。

このサンプルコードは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();

クリップボードにコピーされました。

このサンプルコードは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");

クリップボードにコピーされました。

購入リクエストのConnections.SendRequestディレクティブのJSON構文です。この場合、nameBuyです。

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Buy",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        }
      },
      "token": "correlationToken"
    }
  ]
}

ディレクティブのフィールド

フィールド 説明 必須
type 文字列 ディレクティブのタイプです。購入フローでは常にSendRequestです。
name 文字列 SendRequestメッセージのターゲットです。購入リクエストでは常にBuyです。
payload オブジェクト 指定されたアクションの詳細を含みます。購入リクエストの場合は、InSkillProductプロパティと商品IDが含まれます。
token 文字列 このメッセージの交換を識別しスキル情報を保存するトークンです。このトークンはAlexaでは使用しませんが、Connections.Responseの結果として返されます。このトークンはスキルに合わせた形式で提供します。
shouldEndSession ブール値 trueを送信してセッションが終了することを示します。ただし、SendRequestメッセージが送信された後は常にセッションは終了します。

お勧めを行う

ユーザーがスキル内商品を購入するもう1つの方法は、お勧めを利用することです。つまり、ユーザーが現在スキルと行っている対話に関連した商品を事前に提案します。保存されたリストを参照してユーザーが商品を所有しているかどうかを確認し、お勧めする商品と、商品に関連したメッセージを、Amazonの購入フローに渡します。お勧めを提案するには、次のようにします。

  • ディレクティブを使用して購入フローを開始するコードを追加します。

ユーザーがコンテンツを終了した後、スキルはアップセルリクエストを提示するディレクティブを送信します。スキルセッションが終了し、Alexaはディレクティブに含まれるupsellMessageを話します。
Alexa: 洞窟探検拡張パックを入手すると、冒険をもっと楽しめます。詳しく知りたいですか?
スキルはUpsellリクエストを提示するディレクティブを送信します。これにより現在のスキルセッションが終了します。
ユーザー: はい

買い切り型の場合、購入を確定する前にAmazonからpurchasePromptDescriptionと価格が提供されます。
サブスクリプション型の場合は、購入の確定前に価格が提供されます。

お勧めを開始するディレクティブを送信する

ユーザーインテントに対してお勧めの提案で応答するには、Connections.SendRequestディレクティブをAlexaに送信します。以下のようなものです。

  • upsellを指定してお勧めを提示します。
  • 提案する商品のIDを提供します。
  • お勧めのメッセージプロンプト(upsellMessage)を提供します。
  • スキルの現在の状態やその他の関連情報を保存するトークンが含まれます。これはSendRequest呼び出しからの応答でスキルに戻されます。

upsellMessageでは以下に注意してください。

  • 価格やオファーの詳細を含めてはいけません。 スキルコンテンツの標準価格、サブスクリプション期間、無料お試し期間に言及しないように注意してください。これらの値は商品説明ファイルから取得することが重要です。たとえば、ユーザーがプライム会員の場合、Amazonからディスカウント価格が提示されることがあります。
  • ユーザーが興味を持っているかどうかを判断します。必ずメッセージが明示的な確認の質問で終わるようにします。また、購入するかどうかはたずねないでください。ユーザーは購入するかどうかをこのステップの後で判断します。
  • ヘルプにとどめ、課金機能は提供しません。 商品は提案としてオファーします。今この時点でこの商品が提案される理由は何か、この商品でユーザーに何ができるのかを説明します。
  • あまり頻繁に商品をお勧めしないようにします。 お勧めが会話に割り込んでいるように感じられると、ユーザーを失う恐れがあります。最適なアプローチを見つけるには、徐々に頻度を変更するよりも、十分に間隔をあけて開始します。
  • お勧めは1つにします。 ユーザーが興味を示さない場合は、中断したところから続行します。別の商品をお勧めすることはしないでください。

お勧めを提供する方法の詳細については、お勧めを行う方法を参照してください。

購入フローが開始されると、スキルセッションは終了します。音声対話モデルと、すべての購入メカニズムはAmazon側で処理されます。また、商品説明と標準価格を商品のスキーマから取得します。プライム会員の場合は、メッセージと価格は自動的に調整されます。購入が完了すると、Alexaはスキルを再起動し、購入結果を提示します。

Lambda関数にコードを追加してお勧めのConnections.SendRequestディレクティブを送信する方法の例を以下に示します。

クリップボードにコピーされました。

このサンプルコードは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: "こちらの商品はいかがでしょう。詳しく知りたいですか?",
            },
            token: "correlationToken",
        })
        .getResponse();

クリップボードにコピーされました。

このサンプルコードは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': 'こちらの商品はいかがでしょう。詳しく知りたいですか?'
               },
              'token': 'correlationToken'   
          }
      ],
      'shouldEndSession': true
  }
};

this.emit(":responseReady");

クリップボードにコピーされました。

アップセルのConnections.SendRequestディレクティブのJSON構文です。この場合、nameUpsellであり、payloadにはupsellMessageが含まれます。

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Upsell",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        },
        "upsellMessage": "こちらの商品はいかがでしょう。詳しく知りたいですか?"
      },
      "token": "correlationToken"
    }
  ]
}

ディレクティブのフィールド

フィールド 説明 必須
type 文字列 ディレクティブのタイプです。購入フローでは常にSendRequestです。
name 文字列 SendRequestメッセージのターゲットです。お勧めでは常にUpsellです
payload オブジェクト 指定されたアクションの詳細を含みます。お勧めの場合、これにはInSkillProductプロパティと商品ID、およびupsellMessageプロパティとカスタムメッセージが含まれます。
upsellMessage 文字列 現在のユーザーコンテキストに沿った商品のお勧めです。常に明示的な確認の質問で終わる必要があります。
token 文字列 このメッセージの交換を識別しスキル情報を保存するトークンです。このトークンはAlexaでは使用しませんが、Connections.Responseの結果として返されます。このトークンはスキルに合わせた形式で提供します。
shouldEndSession ブール値 trueを送信してセッションが終了することを示します。ただし、SendRequestメッセージが送信された後は常にセッションは終了します。

購入フロー後にスキルを再開する

購入フローの完了後、Alexaがスキルを再開します。購入結果を処理するコードを追加し、シームレスにスキルを続行します。

スキルは、購入リクエストの結果をConnections.Responseリクエストとして受け取ります。ペイロードに含まれるpurchaseResultに基づいてスキルを再開します。Alexaは、リクエストに渡したtokenも返します。トークンを使って、ユーザーが中断したところからスキルを再開できます。

各結果タイプを処理する方法の詳細については、ガイドラインを参照してください。

購入結果の例

以下のJSONは、商品オファーからのConnections.Responseリクエストを示しています。

{
    "type": "Connections.Response",
    "requestId": "string",
    "timestamp": "string",
    "name": "Upsell",
    "status": {
        "code": "string",
        "message": "string"  
    },
    "payload": {
        "purchaseResult":"ACCEPTED",    
        "productId":"string",   
        "message":"任意の追加メッセージ"
    },
    "token": "string"
}

購入結果の定義

プロパティ 説明

type

リクエストのタイプです。
常にConnections.Responseに設定します。

文字列

requestId

Connections.Responseリクエストの一意のIDです。

文字列

timeStamp

Connections.Responseリクエストの時間です。

文字列

name

この応答が適用されるリクエストの名前です。
UpsellBuyCancel(キャンセルまたは返金の場合)に設定します。

文字列

status

HTTPステータスの結果です。

オブジェクト

status.code

HTTP応答のステータス(200400404など)です。

文字列

status.message

HTTPステータスのメッセージ(OKNot FoundNot Authorizedなど)です。

文字列

payload

購入またはキャンセルトランザクションの結果を含みます。

オブジェクト

payload.purchaseResult

購入またはキャンセルリクエストの結果です。
有効な値は、 ACCEPTEDPENDING_PURCHASEDECLINEDALREADY_PURCHASEDERRORです。

文字列

payload.productId

リクエストで送信されたスキル内商品のIDです。

文字列

payload.message

(オプション)キャンセルまたは購入トランザクションに関するそのほかの詳細です。

文字列

token

購入またはキャンセルフローが完了したときにAlexaが応答で返すオプションの文字列です。たとえば、このトークンを使用して、購入後にスキルを再開するための情報を保存できます。

文字列

購入結果を処理する際のガイドライン

payload.productIdを渡してすべてのスキル内商品を取得するAPIを実行し、応答に返された商品のステータスを確認します。purchaseResultを処理するには、次のガイドラインを参照してください。

結果がACCEPTED

Alexaのセリフ:

  • 買い切り型または消費型: 「はい、 <スキル内商品名>の購入処理が完了しました」
  • サブスクリプション型: 「はい、<スキル内商品名>を使用できます」

スキルの動作:

自動的にリクエストされたコンテンツを開始、またはリクエストされた消費型アイテムを使用します。たとえば「それではプレイしましょう…」や「ではヒントです…」などです。ユーザーが購入を承諾したら、元のリクエストを完了するようにしてください。

消費型商品の場合は、リクエストで指定されたuserIdに関連付けられている永続ストレージにあるユーザーのアイテムのインベントリーを更新する必要もあります。消費型購入のインベントリーの管理を参照してください。

例:

ユーザーが購入を完了しました
Alexa: 洞窟探検を入手しました
スキルが起動します
Alexa: それではプレイしましょう

結果がPENDING_PURCHASE

購入フローに想定よりも時間がかかることがあります。たとえば、現地の銀行規制により、ユーザーは銀行の支払い認証をAlexa購入フローの外部で行わなければならない場合があります。スキルに購入が進行中であることを知らせるには、AlexaはPENDING_PURCHASEを返します。

スキルの動作:

  • その商品の購入ステータスを確認します。応答でユーザーが商品を購入済み(entitled = ENTITLED)であることがわかったら、プレミアムコンテンツで続行します。
  • 未購入の場合は、ユーザーが中断したところまで戻り、ユーザーに続行する方法を提案します。たとえば、ほかの購入済みコンテンツや無料のコンテンツで続行します。
  • 購入が保留中であることには言及しません。
  • 次のスキルセッションの開始時に、ユーザーの購入ステータスを確認します。これは、購入がスキルセッションの外で完了している可能性があるためです。

結果がDECLINED

Alexaのセリフ:

  • 買い切り型または消費型: 「わかりました。」
  • サブスクリプション型: 「はい、わかりました。<スキル内商品名>に申し込みたい時は、いつでも言ってください。」

スキルの動作:

ユーザーが中断したところまで戻り、ユーザーに続行する方法を提案します。

例:

ユーザーが購入を拒否しました
Alexa: わかりました
スキルが起動してからのセリフ
Alexa: コレクションから冒険を選びましょう。どれをプレイしますか?

結果がERROR

Alexaのセリフ:

応答はエラーの原因によって異なります。

  • 「支払い方法が正しく設定されていません。Alexaアプリに設定画面へのリンクを送信しましたので、そちらで確認してみてください。」
  • 「すみません、うまく処理できません。」
  • 「すみません。<コンテンツのタイトル>はご使用の国では利用できません。」
  • 「すみません、このデバイスではスキル内課金を行うことができません。」

スキルの動作:

  • ユーザーが中断したタイミングまで戻って、続行方法を提供します。
  • エラーが発生したことには言及しません。スキルに戻る前に、ユーザーは購入フローから説明を受け取っています。
  • ユーザーにもう一度行うかどうかをたずねないでください。

例:

ユーザーは購入に同意しましたが、支払い情報を更新する必要があります
Alexa: 支払い方法が正しく設定されていません。Alexaアプリに設定画面へのリンクを送信しましたので、そちらで確認してみてください。
スキルが起動します
Alexa: コレクションから冒険を選びましょう。どれをプレイしますか?

結果がALREADY_PURCHASED

この結果は、買い切り型およびサブスクリプション型で返されることがあります。

消費型は商品を複数回購入できるため、消費型でこの結果が返されることはありません。

Alexaのセリフ:

<スキル内商品名>はすでにお持ちです

スキルの動作:

  • リクエストされたコンテンツを自動的に開始します。
  • 別の商品のオファーはしません。

例:

ユーザーがクリスタルキャッチャーを購入するように頼みましたが、ユーザーはすでに持っています。
Alexa: ご存じでしたか?クリスタルキャッチャーはすでにお持ちです。
スキルが起動します
Alexa: コレクションから冒険を選びましょう。どれをプレイしますか?

返金またはキャンセルのリクエストを処理する

購入代金を返金するユーザーリクエストを処理し、リクエストを購入フローに転送できる機能も必要です。

ユーザーは次のように返金を依頼することがあります。

  • アレクサ、<スキル呼び出し名>に<商品名>の返品を頼んで
  • アレクサ、<商品名>を払い戻して
  • <商品名>を返品したい
  • <商品名>の返金を受けたい

例:

ユーザー: アレクサ、洞窟探検を返金して

スキルはCancelリクエストを示すConnections.SendRequestディレクティブを送信します。これにより現在のスキルセッションが終了します。
Alexa: 返金については、Alexaアプリにリンクを送信しましたので、そちらで確認してください。


サブスクリプション型のキャンセル依頼は次のように行われることがあります。

  • 私の<商品名>のサブスクリプションをキャンセルして
  • <商品名>のサブスクリプションをキャンセルして

例:

ユーザー: アレクサ、私の宝探しプラスのサブスクリプションをキャンセルして。

スキルはCancelリクエストを示すConnections.SendRequestディレクティブを送信します。これにより現在のスキルセッションが終了します。
Alexa: わかりました。念のため、宝探しプラスは、ここでしかプレイできない冒険を10個以上集めたもので、毎月新しい冒険が1つ追加されます。ご利用のサービスをキャンセルするんですね?


キャンセルや返金のリクエストをサポートするには、次を実行します。

  • 返金/キャンセルリクエストをサポートするカスタムインテントを作成します
  • カスタムインテントを処理するコードを追加し、ディレクティブを送信してキャンセルフローを開始します

インテントを作成する

以下は、購入を返金するユーザーリクエストをサポートするためのカスタムインテントをモデリングするJSONの例です。サンプルインテントは商品の返品や返金のバリエーションと、スキル内商品のリストを含むカスタムスロットのバリエーションを一覧表示します。

{
  "intents": [
    {
      "name": "RefundSkillItemIntent",
      "samples": [
        "{ProductName}を返品",
        "{ProductName}を返金",
        "{ProductName}の返金を受けたい",
        "{ProductName}を返品したい"
      ],
      "slots": [
        {
          "name": "ProductName",
          "type": "LIST_OF_PRODUCT_NAMES"
        }
      ]
    }
  ],
  "types": [
    {
      "name": "LIST_OF_PRODUCT_NAMES",
      "values": [
        {
          "id": "reference_name",
          "name": {
            "value": "商品A",
            "synonyms": [
              "Aという商品"
            ]
          }
        }
      ]
    }
  ]
}

インテントを処理する

返金/キャンセルインテントのハンドラーで、キャンセルを示すメッセージを購入フローに送信する必要があります。

コードを追加して返金リクエストのConnections.SendRequestディレクティブを送信する方法の例を以下に示します。

クリップボードにコピーされました。

このサンプルコードは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();

クリップボードにコピーされました。

このサンプルコードは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");

クリップボードにコピーされました。

キャンセルまたは返金リクエストのConnections.SendRequestディレクティブのJSON構文です。この場合、nameCancelです。

{
  "directives": [
    {
      "type": "Connections.SendRequest",
      "name": "Cancel",
      "payload": {
        "InSkillProduct": {
          "productId": "amzn1.adg.product.aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
        }
      },
      "token": "correlationToken"
    }
  ]
}

ディレクティブのフィールド

フィールド 説明 必須
type 文字列 ディレクティブのタイプです。購入フローでは常にSendRequestです。
name 文字列 SendRequestメッセージのターゲットです。返金またはキャンセルの場合は常にCancelです。
payload オブジェクト 指定されたアクションの詳細を含みます。キャンセルリクエストの場合は、InSkillProductプロパティと商品IDが含まれます。
token 文字列 このメッセージの交換を識別しスキル情報を保存するトークンです。このトークンはAlexaでは使用しませんが、Connections.Responseの結果として返されます。このトークンはスキルに合わせた形式で提供します。
shouldEndSession ブール値 trueを送信してセッションが終了することを示します。ただし、SendRequestメッセージが送信された後は常にセッションは終了します。

消費型購入のインベントリーの管理

消費型商品を提供する場合は、ユーザーがアイテムを購入したり使用したりするたびに、そのアイテムのインベントリーを管理する必要があります。Alexaは、ユーザーが特定の商品を購入した合計回数は追跡していますが、この回数にはユーザーがその購入済みユニットを消費したかどうかは反映されません。スキルが消費型購入でACCEPTED応答を受け取った場合、以下を行う必要があります。

  • 永続ストレージのユーザーインベントリーを更新して、アイテムの正しい数を反映させます。たとえば、ユーザーが「ヒント5個パック」を購入した場合、ヒントインベントリーに5個のアイテムを追加します。
  • 該当する場合はアイテムを使用し、インベントリーの数を減らします。
  • インベントリーの更新を永続ストレージに保存します。

ASK SDKs for Node.js、Java、およびPythonにはすべて、アトリビュートを保存するPersistenceAdapterがあります。詳細については、ASK SDKドキュメントの永続アトリビュートを参照してください

ユーザーのインベントリーを永続ストレージに保存するときは、常にインベントリーをリクエストに含まれるuserIdにマッピングします。userIdは、リクエストのcontextオブジェクトで提供されます(context.System.User.userId)。SDKで提供されるデフォルトの永続レイヤーは、自動的にuserIdを使用します。

消費型アイテムを提供しインベントリーを管理するスキルの例については、Name the Showを参照してください。

消費型商品の合計課金数を取得する

ユーザーが特定の消費型商品を購入した合計回数を取得するには、InSkillProducts APIを呼び出し、activeEntitlementCountプロパティを確認します。activeEntitlementCountの値は、ユーザーが行った購入の合計回数です。商品を購入するたびにactiveEntitlementCountが1つずつ増えます。キャンセル(支払いエラーや返金リクエストなどのため)のたびにactiveEntitlementCountが1つずつ減ります。

複数のアイテムのパックを提供している場合、購入の合計回数とユーザーがスキルで使えるアイテムの数が合わない場合があります。たとえば、スキルで「ヒント5個パック」を提供しているとします。ユーザーがこのパックを3回購入します。購入1回ごとに、インベントリーを5ずつ増やします。この例では、以下のようになります。

  • InSkillProducts APIから返されるactiveEntitlementCount3です
  • このユーザー用に保有しているインベントリーの数は15です。

その後、ユーザーは15個のヒントのうち、14個を使います。この時点では、以下のようになります。

  • InSkillProducts APIから返されるactiveEntitlementCountは変わらず3です
  • このユーザー用に保有しているインベントリーの数は1となります。

リクエストごとにインベントリーを検証する

ユーザーの購入済み消費型アイテムの数は、通常のスキルセッション外で変更される可能性があります。例

  • 購入フローに想定より時間がかかったが、スキルセッション終了後に正常に完了した場合。ユーザーが次回スキルを開いたとき、activeEntitlementCountはユーザーのインベントリーを反映した数よりも大きい値を返します。
  • ユーザーが間違って購入した商品の返金をリクエストした場合。ユーザーが次回スキルを開いたとき、activeEntitlementCountは前回より小さい値を返します。永続ストレージのユーザーのインベントリーには、返金前のユーザーの購入が反映されています。
  • ユーザーテスト中に、reset-isp-entitlementを使用して自分の購入を消去し、やり直した場合。次にスキルを開くと、activeEntitlementCountは0に戻っていますが、永続ストレージの自分のインベントリーには以前のテスト購入が反映されています。

このような場合は、リクエストを受信するたびにインベントリーを確認して更新します。ASK SDK for Node.js、Java、Pythonには、リクエストインターセプターが含まれます。これらを使用して、通常のリクエストハンドラーの前にコードを実行します。ここで、購入ステータスのチェックを実装します。詳細については、でリクエストインターセプターを参照してください。

ユーザーがスキルを無効化してから再有効化した場合にユーザーインベントリーを管理する

スキルが消費型商品を提供する場合、ユーザーがスキルを無効にしてから再度有効にしても、userIdは変わりません。これにより、このシナリオで保有しているアイテムのインベントリーが失われることはありません。

このシナリオでuserIdにこのような特別な動作をさせるために、何かする必要はありません。ただし、AlexaSkillEvent.SkillDisabledイベントを使用して、ユーザーがスキルを無効にした後に何らかのクリーンアップを実行する場合、ユーザーの消費型購入のインベントリーを維持するようにしてください。イベントオブジェクトでuserInformationPersistenceStatusプロパティを確認できます。

  • PERSISTED: ユーザーがスキルを再度有効にする場合に同じuserIdを使用するために、データを消去しません。
  • NOT_PERSISTED: ユーザーがスキルを再度有効にした場合、新しいuserIdが取得されます。そのため、userIdに伴って保存されたデータを消去しても安全です。

サンプルコード

  • Name the Show – ユーザーにトリビアゲームのヒントとして消費型アイテムを提供する方法を説明するサンプルスキルです。Node.jsのサンプルコードを参照できます。
  • Premium fact skill – Alexaスキルのスキル内課金機能の使用方法を説明するサンプルスキルです。購入を促進するさまざまなパックや、パックを一括でロック解除するサブスクリプションを提供します。Node.jsとPythonのサンプルコードを参照できます。

  • プレミアム「Hello World」スキル – ISP機能の使い方を説明するサンプルスキルです。「Greetings Pack」(挨拶パック)と「Premium Subscription」(プレミアムサブスクリプション)を提供します。 Node.jsとJavaのサンプルコードを参照できます。