0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Alexa スキル内課金(ISP)の商品をSMAPIで操作する

Posted at

SMAPIで操作したことがなかったので、いまさらながらスキル内課金をやってみた。

スキル内課金とは

スキル内課金(ISP:In-Skill Purchasing)とは、カスタムスキル内でプレミアムコンテンツ(デジタルコンテンツ)を販売することができる仕組み。スキルの使用中に、商品を購入するイメージ。

ISPの種類

3種類の課金商品が作成できる。

No. 種類 説明 有効期限
1 買い切り型 1度購入したらずっと使える商品。 なし
2 消費型 定められた回数や期限内で使える商品。回数や期限が過ぎてしまったら再購入する必要がる。 回数や期限
3 サブスクリプション型 定められた間隔(期間)で自動購入される商品。キャンセルするまでの間、定期購読(継続購入)される。 サブスクリプションをキャンセルするまで

ISPの機能

3種類の機能が用意されている。

No. 機能 説明
1 Buy 商品を購入する
2 Upsell 商品を勧める
3 Cancel 商品をキャンセルする

ISPの仕組み

Skill Connections の仕組みが使われている。BuyUpsellCancel はカスタムスキルではなくAlexa側で処理される。

image.png

スキルにISPを追加する

購入やキャンセルに至るまでの通常の会話はインテントモデルにも基づいて作成する。いざ 購入やキャンセルをする! となった場合にISPの仕組みを追加していく。

商品を作成する

商品情報のJSONファイルを作成する。商品情報はJSONで作成し、ファイルとして保存しておく。保存する場所はコマンドラインから参照できるところであればどこでもよい。買い切り型、消費型、サブスクリプション型でJSONの書き方が異なるので、詳しくは:point_right:スキーマ(※ここをクリック)を参照する。

{
  "version": "1.0",
  "type": "CONSUMABLE",
  "referenceName": "life_pack_10",
  "publishingInformation": {
    "locales": {
      "ja-JP": {
        "name": "ライフパック10",
        "smallIconUri": "https://xxxx.com/life_pack_10_smallIconUri.png",
        "largeIconUri": "https://xxxx.com/life_pack_10_largeIconUri.png",
        "summary": "ライフパック10サマリー。",
        "description": "ライフパック10説明。",
        "examplePhrases": [
          "アレクサ、ライフパックを購入する",
          "アレクサ、ライフパックを買う"
        ],
        "keywords": [
          "ゲーム"
        ],
        "customProductPrompts": {
          "purchasePromptDescription": "ライフを追加するよ。",
          "boughtCardDescription": "買ってくれてありがとう。"
        }
      }
    },
    "distributionCountries": [
      "JP"
    ],
    "pricing": {
      "amazon.co.jp": {
        "releaseDate": "2022-02-27T01:25Z",
        "defaultPriceListing": {
          "price": 99,
          "currency": "JPY"
        }
      }
    },
    "taxInformation": {
      "category": "SOFTWARE"
    }
  },
  "privacyAndCompliance": {
    "locales": {
      "ja-JP": {
        "privacyPolicyUrl": "https://xxxx.com/privacy.txt"
      }
    }
  },
  "testingInstructions": "テストの説明",
  "purchasableState": "PURCHASABLE",
  "promotableState": "ALL_AMAZON_CHANNELS"
}

商品を追加するためのリクエスト用JSONを作成する。上記で作成したJSONファイルを使ってリクエスト用のJSONファイルを作成する。ベンダーIDが必要なんですね。

product-request.json
{
  "vendorId": "MA32XWXRJKXIX",
  "inSkillProductDefinition": {

    ※買い切り型 / 消費型 / サブスクリプション型 のJSONスキーマを記載

  }
}

SMAPIを使って商品を登録する。上記で作成したリクエストJSONファイルを使ってスキル内商品を作成する。作成された商品は、ベンダー(ベンダーID)に登録されただけであって、この時点では、まだスキルとは紐づいていない。ただ商品をベンダーに登録しただけ。

# 指定されたベンダーIDに対して新しいスキル内商品を作成する。
ask smapi create-isp-for-vendor --create-in-skill-product-request file:./product_definition.json

# プロダクトIDが返却される
{
  "productId": "amzn1.adg.product.e0db647a-9dcf-4ac3-b382-b5e953bb42e3"
}

商品をスキルに紐づける

上記で返却されたプロダクトIDを使って対象スキルに紐づける。

# スキルとスキル内商品を関連付ける。
ask smapi associate-isp-with-skill --product-id amzn1.adg.product.e0db647a-9dcf-4ac3-b382-b5e953bb42e3 --skill-id amzn1.ask.skill.f837e40e-d552-4041-8d34-bbdbec9bf08b

# 下記が表示されればOK
Command executed successfully!

Lambdaの処理を追加する

バックエンドコードにディレクティブを追加する

BuyUpsellCancel がある。Upsell は商品JSONに含まれる purchasePromptDescription を発話してくれる。
購入に至るまでの流れは、通常のスキル開発と同じでインテントとアレクサレスポンスでやりとりをする。そして購入するぞ!となったときに、ディレクティブを送信することでAlexa側の購入フローに遷移してくれる。

ディレクティブを送信するとセッションが切断される。そのため必要なデータはディレクティブを呼ぶ前に永続ストレージ(DynamoDBやS3)に保存しておく。

例)buy:購入
  return handlerInput.responseBuilder
    .addDirective({
      type: 'Connections.SendRequest',
      name: 'Buy',
      payload: {
        InSkillProduct: {
          productId: 'amzn1.adg.product.e0db647a-9dcf-4ac3-b382-b5e953bb42e3',
        },
      },
      token: 'correlationToken',
    })
    .getResponse();

スキルコネクションズのレスポンスを処理する

スキルコネクションズからのレスポンスは request.typerequest.name で判別します。そして、payload.purchaseResult の内容によって、それに相応する処理を実装します。

例)購入フローからの戻りを受け取る
const BuyResponseHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'Connections.Response' &&
      (handlerInput.requestEnvelope.request.name === 'Buy' ||
        handlerInput.requestEnvelope.request.name === 'Upsell');
  },
  async handle(handlerInput) {
    const locale = handlerInput.requestEnvelope.request.locale;
    const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
    const productId = handlerInput.requestEnvelope.request.payload.productId;

    return ms.getInSkillProducts(locale).then(function handlePurchaseResponse(result) {
      const product = result.inSkillProducts.filter(record => record.productId === productId);

      if (handlerInput.requestEnvelope.request.status.code === '200') {
        switch (handlerInput.requestEnvelope.request.payload.purchaseResult) {
          case 'ACCEPTED': 

            // 購入した場合の処理、発話を組み立てる
            break;
          case 'DECLINED':

            // 購入しなかった場合の処理、発話を組み立てる
            break;
          case 'ALREADY_PURCHASED':

            // 既にその商品を購入している場合の処理、発話を組み立てる
            break;
          default:
            speakOutput = '購入できませんでした。音声ショッピングの設定やお支払い方法をご確認ください。';
            break;

        return handlerInput.responseBuilder
          .speak(speakOutput)
          .reprompt(repromptOutput)
          .getResponse();
      }
      // 処理中にエラーが発生した場合
      console.log(`Connections.Response indicated failure. error: ${handlerInput.requestEnvelope.request.status.message}`);

      return handlerInput.responseBuilder
        .speak('購入処理でエラーが発生しました。もう一度試すか、カスタマーサービスにご連絡ください。')
        .getResponse();
    });
  },
};

テストと申請の観点

観点が公開されているので、これ等を全てクリアする。

参考:ISPを操作するためのSMAPIコマンド

詳細は、ASK CLIコマンドリファレンスを参照。

# ==== 商品確認 ====
# Amazonアカウントに関連付けられたベンダーIDに紐づいている商品のリストを表示する。
ask smapi get-isp-list-for-vendor

# スキルに紐づいている商品の一覧を表示する。
ask smapi get-isp-list-for-skill-id --skill-id amzn1.ask.skill.45f6f820-5e75-43f4-994a-b560c6a8803c

# 更新された商品情報を表示する
$ ask smapi get-isp-summary --product-id amzn1.adg.product.9333d53e-d66d-44d3-9ab6-1c73b1f6dee8 --stage development


# ==== 商品作成 ====
# 指定されたベンダーIDに対して新しいスキル内商品を作成する。
ask smapi create-isp-for-vendor --create-in-skill-product-request file:./product_definition.json

# プロダクトIDが返却される。
{
  "productId": "amzn1.adg.product.e0db647a-9dcf-4ac3-b382-b5e953bb42e3"
}

# スキルとスキル内商品を関連付ける。
ask smapi associate-isp-with-skill --product-id  amzn1.adg.product.9333d53e-d66d-44d3-9ab6-1c73b1f6dee8 --skill-id amzn1.ask.skill.45f6f820-5e75-43f4-994a-b560c6a8803c

# 下記が表示されればOK
Command executed successfully!


# ==== 商品情報更新 ====
# Update the product definition
$ ask smapi update-isp-for-product --product-id amzn1.adg.product.9333d53e-d66d-44d3-9ab6-1c73b1f6dee8 --in-skill-product file:./treasure_finder.json --stage development


# ==== 商品削除 ====
# "productId"のスキル内商品を削除する。
# ※削除対象のスキル内商品が、まだスキルと紐づいているままの場合は商品を削除できない。
ask smapi delete-isp-for-product --product-id amzn1.adg.product.9333d53e-d66d-44d3-9ab6-1c73b1f6dee8 --stage development

# 下記が表示されればOK
Command executed successfully! 

# disassociate-isp-with-skill を使用してリンクを解除する。
ask smapi disassociate-isp-with-skill --product-id amzn1.adg.product.71eff630-b77b-4a99-9020-d62b118493d7 --skill-id amzn1.ask.skill.b130557b-0306-4512-ad1e-65a0d4023089

# 下記が表示されればOK
Command executed successfully!


# ==== スキル内商品がどのスキルで使われているかを確認する ====
# 商品が紐づいているスキルを表示する
ask smapi get-isp-associated-skills --product-id amzn1.adg.product.d3889d23-8273-4d88-9781-55b1ae5ab279 --stage development

# スキル情報の取得
ask smapi list-skills-for-vendor --skill-id amzn1.ask.skill.b130557b-0306-4512-ad1e-65a0d4023089

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?