[レポート] Alexaスキルで Amazon Payを導入してみように参加してきました #AlexaDevSumi

こんにちは、Mr.Moです。

Alexa Dev Summit Tokyo 2018 内で開催されたAmazon Payのハンズオンに参加してきましたのでまとめていきます。

今回のハンズオンのゴール

  • Amazon Payを使った簡単なAlexaスキルを作ります。
  • AlexaのConnectionsを通じたAmazon Payの各種APIの使い方を学びます。

Amazon Pay とは

Amazon以外のサイトでも、Amazonアカウントを使って、簡単・安全にお買い物の決済ができるサービスです。

https://pay.amazon.com/jp/shopper

ハンズオンで利用するAmazon PayのAPI

今回のハンズオンでは下記のAPIを使用します。

事前準備

必要なアカウント

今回のハンズオンに必要なアカウントは下記になります。

今回のスキル開発の流れ

以下の順番で開発をしていきます。 スキルのベースはAlexaハンズオンでもお馴染みのコーヒーショップスキルです。

  1. Amazon Lambdaを用意
  2. Alexaスキル用意
  3. Amazon Pay Seller Centralの設定
  4. LambdaのコードでAmazon PayのAPIを使用

上記1.と2.は従来のAlexaスキル開発と同じなので、本ブログでは割愛いたします。

なお、Alexa Developer ConsoleでAmazon Payを有効にしておきます。

Amazon Pay Seller Centralの設定

下記のように設定していきます。

ここでついでに出品者IDをメモしておきます。

テストアカウント作成

今回のハンズオンではひとまずテストアカウントを使用するので下記のように作成していきます。

LambdaのコードでAmazon PayのAPIを使用

いったんスキルの処理の中から主な部分をピックアップして説明していきます。 これから記載するコードは、ハンズオンで配布されたサンプルから抜粋したものになります。

Amazon Payのアクセス権限確認

まずAmazon Payの権限確認を行います。

権限が有効でないケースに対応するため下記のようにAmazon Payの有効をユーザに促す処理を組み込んでおきます。

const permissions = handlerInput.requestEnvelope.context.System.user.permissions;
const amazonPayPermission = permissions.scopes['payments:autopay_consent'];
console.log("amazonPayPermission", amazonPayPermission);
if (amazonPayPermission.status === "DENIED")
    return sendAmazonPayPermissionCard(handlerInput);

const sendAmazonPayPermissionCard = (handlerInput) => {
    return handlerInput.responseBuilder
        .speak("Amazon Payの権限が有効になっておりません。Alexaアプリにカードを送りましたのでそのカードからAmazon Payを有効にしてください。")
        .withAskForPermissionsConsentCard(['payments:autopay_consent'])
        .getResponse();
}

決済処理事前準備(Setup API)

Amazon Payの権限が有効であれば、決済処理事前準備を進めていきます。

下記のようなコードで決済処理事前準備を実行します。 ここでは、さきほどメモしておいた出品者IDの値テストアカウント作成時に指定したメールアドレスを使って、Setup API用のPayload作成処理を構築します。

  • config.js
exports.AMAZON_PAY = {
  MERCHANT_ID: "<出品者IDを指定>",
  VERSION: "1.0",
  COUNTRY_OF_ESTABLISHMENT: "JP",
  LEDGER_CURRENCY: "JPY",
  CHECKOUT_LANGUAGE: "ja_JP",
  NEED_AMAZON_SHIPPING_ADDRESS: false,
  SANDBOX_CUSTOMER_EMAIL: "<テストアカウントのメールアドレスを指定>",
  SANDBOX: true,
  INSTANT_CHECKOUT: false,
  // PROCESS PAYMENT
  PAYMENT_ACTION: "AuthorizeAndCapture",
  // PAYMENT_ACTION: "Authorize",
  // AUTHORIZE ATTRIBUTES
  TRANSACTION_TIMEOUT: 0,
  // SELLER ORDER ATTRIBUTES
  SELLER_ORDER_ID: "",
  //BILLING AGREEMENT
  SELLER_BILLING_AGREEMENT_ID: ''
};
  • payload-builder.js
const config = require('./config').AMAZON_PAY;

const setupPayloadVersioning = {
    type: 'SetupAmazonPayRequest',
    version: "2"
}

var setupPayload = function () {

    var payload = {};
    payload['@type'] = setupPayloadVersioning.type;
    payload['@version'] = setupPayloadVersioning.version;
    payload['checkoutLanguage'] = config.CHECKOUT_LANGUAGE;
    let sellerBillingAgreementAttributes = {
        "@type": "SellerBillingAgreementAttributes",
        "@version": "2",
        "storeName": "storeName",
        "customInformation": "customInformation"
    };
    payload['billingAgreementAttributes'] = {
        "@type": "BillingAgreementAttributes",
        "@version": "2",
        "sellerNote": 'sellerNote',
        "sellerBillingAgreementAttributes" : sellerBillingAgreementAttributes
    }
    payload['sellerId'] = config.MERCHANT_ID;
    payload['countryOfEstablishment'] = config.COUNTRY_OF_ESTABLISHMENT;
    payload['ledgerCurrency'] = config.LEDGER_CURRENCY
    payload['sandboxCustomerEmailId'] = config.SANDBOX_CUSTOMER_EMAIL;
    payload['sandboxMode'] = config.SANDBOX;
    payload['needAmazonShippingAddress'] = config.NEED_AMAZON_SHIPPING_ADDRESS;
    // payload['instantCheckout'] = config.INSTANT_CHECKOUT;

    return payload;
};

module.exports = {
    'setupPayload': setupPayload,
    'chargePayload': chargePayload
};
  • index.js
const attributes = handlerInput.attributesManager.getSessionAttributes();

let setupDirectiveObject = {
    "type": "Connections.SendRequest",
    "name": "Setup",
    "payload": payloadBuilder.setupPayload(),
    "token": JSON.stringify(attributes) // SessionAttributeを文字列にして保存しておく
};

return handlerInput.responseBuilder
    .addDirective(setupDirectiveObject) // setupDirectiveObjectを追加します
    .withShouldEndSession(true)
    .getResponse();

なお、上記の「Connections.SendRequest」を呼び出すタイミングでセッションが切れるとのことなので、sessionスコープに持たせるデータはDyanamoDBに格納するようにするのが良さそうです。

決済処理(Charge API)

最後に決済処理の実行です。

下記のようなコードで、Charge API用のPayload作成処理を構築します。

  • payload-builder.js
const processPayloadVersioning = {
    type: 'ChargeAmazonPayRequest',
    version: "2"
}

var chargePayload = function (billingAgreementId, authorizationReferenceId, sellerOrderId, amount) {

    var payload = {};
    payload['@type'] = processPayloadVersioning.type;
    payload['@version'] = processPayloadVersioning.version;
    payload['billingAgreementId'] = billingAgreementId;
    payload['paymentAction'] = config.PAYMENT_ACTION;;
    payload['sellerId'] = config.MERCHANT_ID;
    payload['authorizeAttributes'] = {
        "@type": "AuthorizeAttributes",
        "@version": "2",
        "authorizationReferenceId": authorizationReferenceId,
        "authorizationAmount": {
            "@type": "Price",
            "@version": "2",
            "amount": amount.toString(),
            "currencyCode": config.LEDGER_CURRENCY
        },
        'transactionTimeout': config.TRANSACTION_TIMEOUT,
        "sellerAuthorizationNote": 'sellerAuthorizationNote',
        "softDescriptor": "softDescriptor",
    }
    payload['sellerOrderAttributes'] = {
        "@type": "SellerOrderAttributes",
        "@version": "2",
        'sellerOrderId': sellerOrderId,
        "storeName": "storeName",
        'customInformation': "customInformation",
        'sellerNote': 'sellerNote'
    }
    return payload;
};

module.exports = {
    'setupPayload': setupPayload,
    'chargePayload': chargePayload
};
  • index.js
const actionResponsePayload = handlerInput.requestEnvelope.request.payload;
const billingAgreementDetails = actionResponsePayload.billingAgreementDetails;
let billingAgreementID = billingAgreementDetails.billingAgreementId;

const attributes = handlerInput.attributesManager.getSessionAttributes();
attributes.billingAgreementID = billingAgreementID;

let sellerOrderId = "alexa-seller-order-id"; //今は固定
let chargeDirectiveObject = {
    "type": "Connections.SendRequest",
    "name": "Charge",
    "payload": payloadBuilder.chargePayload(
        attributes.billingAgreementID, // お支払い用オブジェクト Billing Agreement ID
        generateRandomString(10), // Unique な IDを生成する
        sellerOrderId, // 事業者側の注文ID
        price // 請求する金額
    ),
    "token": JSON.stringify(attributes)
};

return handlerInput.responseBuilder
    .addDirective(chargeDirectiveObject)
    .speak("では、決済処理を始めます")
    .withShouldEndSession(true)
    .getResponse();

なお、決済まわりの処理には適切にエラーハンドリングの処理を組み込んでおく必要がありそうです。

さっそく動かしてみる

Alexaスキルでコーヒーを購入することができました!

まとめ

Alexaスキル内でAmazon PayのAPIを使用すると既存のスキルに購入・支払いの機能を追加することも簡単にできそうですね。 今回のようなハンズオンは今後も開催を予定されているとのことなので今回参加されていない方も次回は是非参加してみてはいかがでしょうか。