スキルコードにISPのサポートを追加する



スキルコードにISPのサポートを追加する

スキル内課金(ISP)をスキルに追加する場合は、以下を行う必要があります。

  • スキル内商品を呼び出して商品インベントリーを取得する
  • 購入を可能にするカスタムインテントと、それらのインテントのハンドラーを、スキルコードに提供する
  • スキルコードから購入フローに、購入する商品を指定するディレクティブを送信する
  • 購入フローの完了後、スキルを再開する
  • 消費型を購入する場合、ユーザーのアイテムのインベントリーを永続ストレージに保存し、ユーザーがアイテムを使用するごとにインベントリーを管理する

このトピックでは、手順、コードのサンプル、ベストプラクティス、詳細情報へのリンクを提供します。

前提条件

このドキュメントの手順を完了するには、以下が必要です。

  • カスタム対話モデルを使用し、Lambda関数に関連付けられているスキル。詳細については、カスタムスキルのビルド手順を参照してください。ウェブサービスとしてホストされるスキルにもスキル内商品を追加できますが、このトピックのコードは、AWS Lambda関数にスキル内商品を追加する方法を説明しています。
  • 少なくとも1つのスキル内商品がカスタムスキルに追加されていること。商品の設定は開発者コンソールで、またはASK CLIを使用して行うことができます。

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

スキルで商品を提供するには、現在のスキルセッションで、ユーザーが使用できる商品のインベントリーを取得する必要があります。リストを取得したら、それを使用して以下のことを行います。

  • 各商品の商品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": "BuySkillItemIntent",
      "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つのシナリオを処理する必要があります。

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

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

例:

ユーザー: 何を買えるの?
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側で処理されます。また、商品説明と標準価格を商品のスキーマから取得します。プライム会員の場合は、メッセージと価格は自動的に調整されます。購入が完了するとスキルが再度起動し、購入結果がスキルに提供されます。

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メッセージが送信された後は常にセッションは終了します。

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

ユーザーエクスペリエンスがスムーズになるように、購入フローの結果を正しく処理してスキルを再開する必要があります。

これには、以下を実行します。

  • Connections.Responseリクエストを処理します

購入フローの結果は、購入が成功したかどうかを示すConnections.Responseリクエストです。また、リクエスト内で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"
}

Connection.Responseリクエストのフィールド

名前 説明 必須
type 文字列 ディレクティブのタイプです。購入フローの応答では常にConnections.Responseです。
requestId 文字列 このリクエストの一意のIDです。
timeStamp 文字列 このリクエストが発行された時間です。
name 文字列 SendRequestメッセージのターゲットを示します。 お勧めの場合はUpsell、購入リクエストの場合はBuy、キャンセルまたは返金リクエストの場合はCancelです。
status オブジェクト HTTPステータスの詳細を提供します。
status.code 文字列 HTTP応答のステータス(200、400、404など)です。  
status.message 文字列 HTTP応答のメッセージ(OK、Not Found、Not Authorizedなど)です。  
payload オブジェクト 購入トランザクション固有の詳細を含みます。
payload.purchaseResult 文字列 購入トランザクションの結果を示します。ACCEPTEDDECLINEDALREADY_PURCHASEDERRORのいずれかです。
payload.productId 文字列 購入メッセージまたはコンテキストメッセージで送信されたスキル内商品です。
payload.message 文字列 トランザクションのそのほかの詳細を含みます。
token 文字列 購入リクエストが行われたときにスキルから渡されたトークンです。

InSkillProduct APIを使用してpurchaseResult内で返された商品のステータスをチェックし、次のガイドラインを使用してpurchaseResultを処理する必要があります。

結果がACCEPTED

Alexaのセリフ:

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

スキルの動作:

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

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

例:

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

結果が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は前回より小さい値を返します。永続ストレージのユーザーのインベントリーには、返金前のユーザーの購入が反映されています。
  • ユーザーテスト中に、reset-isp-entitlementを使用して自分の購入を消去し、やり直した場合。次にスキルを開くと、activeEntitlementCountは0に戻っていますが、永続ストレージの自分のインベントリーには以前のテスト購入が反映されています。

このような場合は、リクエストを受信するたびにインベントリーを確認して更新します。ASK SDKs for Node.js、Java、Pythonにはすべて、リクエストインターセプターが含まれています。これは通常のリクエストハンドラーの前にコードを実行するのに便利な方法です。このインベントリー確認の実装に適した方法でもあります。詳細については、ASK SDKドキュメントでリクエストインターセプターを参照してください

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

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

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

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