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


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

スキル内課金(ISP)をカスタムスキルに追加する場合、スキルロジックに購入フローを追加してAlexaに制御を引き渡すことで、ユーザーに購入手順を案内できるようになります。

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

前提条件

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

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

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

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

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

購入可能なスキル内商品のリストを取得するには、スキル内商品をリスト化するAPIを使用します。この呼び出しを起動リクエストハンドラーに追加し、スキルセッション中にこの商品のリストを保存します。リクエストが成功すると、メッセージ結果には、フィルター条件に該当する商品のリストが含まれます。

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

このサンプルコードは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'); // 最後のカンマを「and」に置き換えます
  return productListSpeech;
}

/*
  リクエストハンドラーです。このハンドラーは、ユーザーが特定のインテントを指定せずに
  スキルを開始した場合に使用されます。
*/
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("Something went wrong in loading product list.");
    }
    // 取得した商品リストに対するアクション
    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のbreakタグを使用して、選択肢を区別しやすくします。

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

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

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

ユーザーが商品を直接指定する場合もあれば、商品を選択するようにスキルが促す場合もあります。いずれにしても、ユーザーが購入する商品を指定したら、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)を指定する。
  • スキルの現在の状態やその他の関連情報を保存するトークンが含まれる。Alexaは、SendRequest呼び出しからの応答で、スキルにトークンを戻します。

アップセルメッセージに含める内容の詳細については、お勧めをデザインするを参照してください。

購入フローが開始されると、スキルセッションは終了します。Amazonは、音声対話モデルと、購入のあらゆるしくみをサポートしています。Alexaはアップセルメッセージを再生し、ユーザーが確認したら、商品スキーマから取得した商品説明と標準価格を再生します。購入が完了すると、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を使用します。

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

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

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

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

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

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

  • 購入フローに想定より時間がかかったが、スキルセッション終了後に正常に完了した場合。ユーザーが次回スキルを開いたとき、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で保存されたデータを消去しても安全です。

サンプルコード

次のサンプルコードでは、さまざまな支払いモデルの商品でスキル内課金を使用する方法を示しています。


このページは役に立ちましたか?

最終更新日: 2025 年 10 月 14 日