このガイドでは、Orders API を使用して予約を行う Actions プロジェクトの開発プロセスについて説明します。
取引の流れ
Actions プロジェクトで予約を処理する場合、次のフローを使用します。
- 取引要件を検証する(省略可) - 会話の開始時に取引要件ヘルパーを使用して、ユーザーが取引を実行できることを確認します。
- 注文を作成する - 予約の詳細を作成する「カート アセンブリ」をユーザーに案内します。
- 注文を提案する - カートが完成したら、ユーザーに予約の「注文」を提案して、注文が正しいことを確認します。予約が確定すると、予約の詳細を含むレスポンスが返されます。
- 注文を確定して領収書を送信する - 注文が確定したら、予約システムを更新し、ユーザーに領収書を送信します。
- 注文の更新情報を送信する - 予約の有効期間中は、PATCH リクエストを Orders API に送信して、ユーザーに予約のステータスの更新情報を送信します。
制限と審査のガイドライン
取引と Orders API を使用するアクションには追加のポリシーが適用されることにご注意ください。取引を含むアクションのレビューには最大で 6 週間ほどかかります。リリース スケジュールを計画する際は、この点を考慮してください。レビュー プロセスがスムーズに進むよう、レビューにアクションを提出する前に、取引に関するポリシーとガイドラインに準拠していることを確認してください。
Orders API を使用するアクションは、次の国でのみデプロイできます。
オーストラリア ブラジル カナダ インドネシア |
日本 メキシコ カタール ロシア |
シンガポール スイス タイ トルコ 英国 米国 |
プロジェクトのビルド
取引に関連する会話の例については、Node.js と Java の取引のサンプルをご覧ください。
プロジェクトの設定
アクションを作成するときに、Actions Console でトランザクションを実行することを指定する必要があります。また、Node.JS クライアント ライブラリを使用している場合は、最新バージョンの Orders API を使用するようにフルフィルメントを設定します。
プロジェクトとフルフィルメントを設定する手順は次のとおりです。
- 新しいプロジェクトを作成するか、既存のプロジェクトをインポートします。
- [Deploy](デプロイ)> [Directory information](ディレクトリ情報)の順に移動します。
[その他の情報] > [トランザクション] で、[アクションで Transactions API を使用して物理的な商品のトランザクションを実行しますか?] チェックボックスをオンにします。
Node.JS クライアント ライブラリを使用してアクションのフルフィルメントを作成する場合は、フルフィルメント コードを開き、アプリのデカレーションを更新して
ordersv3
フラグをtrue
に設定します。次のコード スニペットは、Orders バージョン 3 のアプリ宣言の例を示しています。
Node.js
const {dialogflow} = require('actions-on-google'); let app = dialogflow({ clientId, // If using account linking debug: true, ordersv3: true, });
Node.js
const {actionssdk} = require('actions-on-google'); let app = actionssdk({ clientId, // If using account linking debug: true, ordersv3: true, });
1. 取引要件を確認する(省略可)
ユーザー エクスペリエンス
ユーザーが予約を設定する意思を示したらすぐに actions.intent.TRANSACTION_REQUIREMENTS_CHECK
インテントをトリガーし、ユーザーが予約をリクエストできるようにすることをおすすめします。たとえば、アクションが呼び出されたときに、「座席を予約しますか?」と質問することがあります。ユーザーが「はい」と答えた場合は、すぐにこのインテントをリクエストする必要があります。これにより、先に進むだけでなく、取引の続行を妨げる設定を修正する機会を用意できます。
取引要件チェック インテントをリクエストすると、次のいずれかの結果になります。
- 要件を満たしている場合、フルフィルメントは成功を示すインテントを受け取り、ユーザーの注文の作成に進むことができます。
1 つ以上の要件が満たされていない場合、フルフィルメントは失敗状態を含むインテントを受け取ります。その場合は、会話を終了するか、予約フローから離れてください。
ユーザーがエラーを修正できる場合は、デバイスで問題の解決を求めるメッセージが自動的に表示されます。スマート スピーカーなどの音声のみのサーフェスで行われている場合、会話はユーザーのスマートフォンに渡されます。
フルフィルメント
ユーザーが取引の要件を満たしていることを確認するには、TransactionRequirementsCheckSpec オブジェクトを使用して actions.intent.TRANSACTION_REQUIREMENTS_CHECK
インテントのフルフィルメントをリクエストします。
要件を確認する
クライアント ライブラリを使用して、ユーザーが予約要件を満たしているかどうかを確認します。
conv.ask(new TransactionRequirements());
return getResponseBuilder(request) .add("Placeholder for transaction requirements text") .add(new TransactionRequirements()) .build();
下記の JSON は Webhook レスポンスを示します。
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } } } }
下記の JSON は Webhook レスポンスを示します。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "inputValueData": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } ] } ] }
要件確認の結果を受け取る
アシスタントはインテントを遂行した後、チェックの結果を含む actions.intent.TRANSACTION_REQUIREMENTS_CHECK
インテントでリクエストをフルフィルメントに送信します。
このリクエストを適切に処理するには、actions_intent_TRANSACTION_REQUIREMENTS_CHECK
イベントによってトリガーされる Dialogflow インテントを宣言します。トリガーされると、このインテントをフルフィルメントで処理します。
const arg = conv.arguments.get('TRANSACTION_REQUIREMENTS_CHECK_RESULT'); if (arg && arg.resultType === 'CAN_TRANSACT') { // Normally take the user through cart building flow conv.ask(`Looks like you're good to go!`); } else { conv.close('Transaction failed.'); }
Argument transactionCheckResult = request .getArgument("TRANSACTION_REQUIREMENTS_CHECK_RESULT"); boolean result = false; if (transactionCheckResult != null) { Map<String, Object> map = transactionCheckResult.getExtension(); if (map != null) { String resultType = (String) map.get("resultType"); result = resultType != null && resultType.equals("CAN_TRANSACT"); } } ResponseBuilder responseBuilder = getResponseBuilder(request); if (result) { responseBuilder.add("Looks like you're good to go! Now say 'confirm transaction'"); } else { responseBuilder.add("Transaction failed"); } return responseBuilder.build();
下記の JSON は Webhook リクエストを示します。
{ "responseId": "", "queryResult": { "queryText": "", "action": "", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "", "fulfillmentMessages": [], "outputContexts": [], "intent": { "name": "reservation_transaction_check_complete_df", "displayName": "reservation_transaction_check_complete_df" }, "intentDetectionConfidence": 1, "diagnosticInfo": {}, "languageCode": "" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "isInSandbox": true, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "inputs": [ { "rawInputs": [], "intent": "", "arguments": [ { "extension": { "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult", "resultType": "CAN_TRANSACT" }, "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT" } ] } ], "user": {}, "conversation": {}, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] } }, "session": "" }
下記の JSON は Webhook リクエストを示します。
{ "user": {}, "device": {}, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "conversation": {}, "inputs": [ { "rawInputs": [], "intent": "reservation_transaction_check_complete_asdk", "arguments": [ { "extension": { "@type": "type.googleapis.com/google.transactions.v3.TransactionRequirementsCheckResult", "resultType": "CAN_TRANSACT" }, "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT" } ] } ], "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] }
2. 注文を作成する
ユーザー エクスペリエンス
必要なユーザー情報を取得したら、ユーザーが予約を作成できるようにする「カート アセンブリ」エクスペリエンスを構築します。カート アセンブリのフローは、アクションごとにサービスに合わせて若干異なります。
基本的なカート構成では、ユーザーはリストから予約に追加するオプションを選択しますが、ユーザー エクスペリエンスを簡素化するように会話を設計することもできます。たとえば、「はい」か「いいえ」で答えられる簡単な質問でユーザーが毎月の予約をスケジュールできるカート アセンブリ エクスペリエンスを構築します。ユーザーに「おすすめ」予約のカルーセルまたはリストカードを表示することもできます。
リッチ レスポンスを使用して、ユーザーの選択肢を視覚的に提示することをおすすめしますが、ユーザーが音声だけでカートを作成できるように会話を設計することもできます。カート アセンブリのエクスペリエンスに関するベスト プラクティスと例については、トランザクション設計ガイドラインをご覧ください。
フルフィルメント
会話全体を通して、ユーザーが購入を希望する予約の詳細を収集し、Order
オブジェクトを作成します。
Order
には、少なくとも次の内容を含める必要があります。
buyerInfo
- 予約をスケジュールするユーザーに関する情報。transactionMerchant
- 予約を推進する販売者に関する情報。contents
-lineItems
として表示される予約の実際の詳細。
カートを作成するには、Order
レスポンス ドキュメントをご覧ください。予約に応じて、異なるフィールドを含める必要がある場合があります。
次のサンプルコードは、省略可能なフィールドを含む完全な予約注文を示しています。
app.intent('build_reservation_df', (conv) => { const now = new Date().toISOString(); const order = { createTime: now, lastUpdateTime: now, merchantOrderId: 'UNIQUE_ORDER_ID', userVisibleOrderId: 'USER_VISIBLE_ORDER_ID', transactionMerchant: { id: 'https://www.example.com', name: 'Example Merchant', }, contents: { lineItems: [ { id: 'LINE_ITEM_ID', name: 'Dinner reservation', description: 'A world of flavors all in one destination.', reservation: { status: 'PENDING', userVisibleStatusLabel: 'Reservation is pending.', type: 'RESTAURANT', reservationTime: { timeIso8601: '2020-01-16T01:30:15.01Z', }, userAcceptableTimeRange: { timeIso8601: '2020-01-15/2020-01-17', }, partySize: 6, staffFacilitators: [ { name: 'John Smith', }, ], location: { zipCode: '94086', city: 'Sunnyvale', postalAddress: { regionCode: 'US', postalCode: '94086', administrativeArea: 'CA', locality: 'Sunnyvale', addressLines: [ '222, Some other Street', ], }, }, }, }, ], }, buyerInfo: { email: 'janedoe@gmail.com', firstName: 'Jane', lastName: 'Doe', displayName: 'Jane Doe', }, followUpActions: [ { type: 'VIEW_DETAILS', title: 'View details', openUrlAction: { url: 'https://example.com', }, }, { type: 'CALL', title: 'Call us', openUrlAction: { url: 'tel:+16501112222', }, }, { type: 'EMAIL', title: 'Email us', openUrlAction: { url: 'mailto:person@example.com', }, }, ], termsOfServiceUrl: 'https://www.example.com', };
private static OrderV3 createOrder() { // Transaction Merchant MerchantV3 transactionMerchant = new MerchantV3() .setId("http://www.example.com") .setName("Example Merchant"); // Line Item // Reservation Item Extension ReservationItemExtension reservationItemExtension = new ReservationItemExtension() .setStatus("PENDING") .setUserVisibleStatusLabel("Reservation pending.") .setType("RESTAURANT") .setReservationTime(new TimeV3() .setTimeIso8601("2020-01-16T01:30:15.01Z")) .setUserAcceptableTimeRange(new TimeV3() .setTimeIso8601("2020-01-15/2020-01-17")) .setPartySize(6) .setStaffFacilitators(Collections.singletonList(new StaffFacilitator() .setName("John Smith"))) .setLocation(new Location() .setZipCode("94086") .setCity("Sunnyvale") .setPostalAddress(new PostalAddress() .setRegionCode("US") .setPostalCode("94086") .setAdministrativeArea("CA") .setLocality("Sunnyvale") .setAddressLines( Collections.singletonList("222, Some other Street")))); LineItemV3 lineItem = new LineItemV3() .setId("LINE_ITEM_ID") .setName("Dinner reservation") .setDescription("A world of flavors all in one destination.") .setReservation(reservationItemExtension); // Order Contents OrderContents contents = new OrderContents() .setLineItems(Collections.singletonList(lineItem)); // User Info UserInfo buyerInfo = new UserInfo() .setEmail("janedoe@gmail.com") .setFirstName("Jane") .setLastName("Doe") .setDisplayName("Jane Doe"); // Follow up actions Action viewDetails = new Action() .setType("VIEW_DETAILS") .setTitle("View details") .setOpenUrlAction(new OpenUrlAction() .setUrl("https://example.com")); Action call = new Action() .setType("CALL") .setTitle("Call us") .setOpenUrlAction(new OpenUrlAction() .setUrl("tel:+16501112222")); Action email = new Action() .setType("EMAIL") .setTitle("Email us") .setOpenUrlAction(new OpenUrlAction() .setUrl("mailto:person@example.com")); // Terms of service and order note String termsOfServiceUrl = "https://example.com"; String now = Instant.now().toString(); OrderV3 order = new OrderV3() .setCreateTime(now) .setLastUpdateTime(now) .setMerchantOrderId("UNIQUE_ORDER_ID") .setUserVisibleOrderId("UNIQUE_USER_VISIBLE_ORDER_ID") .setTransactionMerchant(transactionMerchant) .setContents(contents) .setBuyerInfo(buyerInfo) .setFollowUpActions(Arrays.asList( viewDetails, call, email )) .setTermsOfServiceUrl(termsOfServiceUrl); return order; }
下記の JSON は Webhook レスポンスを示します。
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_DECISION", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "order": { "createTime": "2019-07-17T18:25:30.182Z", "lastUpdateTime": "2019-07-17T18:25:30.182Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" }, "orderOptions": { "requestDeliveryAddress": false, "userInfoOptions": { "userInfoProperties": [ "EMAIL" ] } }, "presentationOptions": { "actionDisplayName": "RESERVE" } } } } } }
3. 注文内容を表示する
予約注文をユーザーに提示して、ユーザーが確認または拒否できるようにします。actions.intent.TRANSACTION_DECISION
インテントをリクエストし、作成した Order
を指定します。
ユーザー エクスペリエンス
actions.intent.TRANSACTION_DECISION
インテントをリクエストすると、アシスタントは組み込みエクスペリエンスを開始し、Order
がカートのプレビュー カードに直接レンダリングされます。ユーザーは「予約のスケジュールを設定」と言う、取引を承認しない、予約の詳細の変更をリクエストするなどの操作ができます。
ユーザーはこの時点で注文内容の変更をリクエストできます。その場合は、フルフィルメントで、カート アセンブリのエクスペリエンスが完了した後に注文変更リクエストを処理できるようにする必要があります。
フルフィルメント
actions.intent.TRANSACTION_DECISION
インテントをリクエストしたら、Order
と orderOptions
を含む TransactionDecision
を作成します。
次のコードは、注文の TransactionsDecision
の例を示しています。
conv.ask(new TransactionDecision({ orderOptions: { requestDeliveryAddress: 'false', }, presentationOptions: { actionDisplayName: 'RESERVE', }, order: order, }));
// Create order options OrderOptionsV3 orderOptions = new OrderOptionsV3() .setRequestDeliveryAddress(false) .setUserInfoOptions(new UserInfoOptions() .setUserInfoProperties(Collections.singletonList("EMAIL"))); // Create presentation options PresentationOptionsV3 presentationOptions = new PresentationOptionsV3() .setActionDisplayName("RESERVE"); // Ask for transaction decision return getResponseBuilder(request) .add("Placeholder for transaction decision text") .add(new TransactionDecision() .setOrder(order) .setOrderOptions(orderOptions) .setPresentationOptions(presentationOptions) ) .build();
下記の JSON は Webhook レスポンスを示します。
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_DECISION", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "orderOptions": { "requestDeliveryAddress": "false" }, "presentationOptions": { "actionDisplayName": "RESERVE" }, "order": { "createTime": "2019-07-17T18:25:30.184Z", "lastUpdateTime": "2019-07-17T18:25:30.184Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" } } } } } }
下記の JSON は Webhook レスポンスを示します。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TRANSACTION_DECISION", "inputValueData": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec", "orderOptions": { "requestDeliveryAddress": "false" }, "presentationOptions": { "actionDisplayName": "RESERVE" }, "order": { "createTime": "2019-07-17T18:25:30.057Z", "lastUpdateTime": "2019-07-17T18:25:30.057Z", "merchantOrderId": "UNIQUE_ORDER_ID", "userVisibleOrderId": "USER_VISIBLE_ORDER_ID", "transactionMerchant": { "id": "https://www.example.com", "name": "Example Merchant" }, "contents": { "lineItems": [ { "id": "LINE_ITEM_ID", "name": "Dinner reservation", "description": "A world of flavors all in one destination.", "reservation": { "status": "PENDING", "userVisibleStatusLabel": "Reservation is pending.", "type": "RESTAURANT", "reservationTime": { "timeIso8601": "2020-01-16T01:30:15.01Z" }, "userAcceptableTimeRange": { "timeIso8601": "2020-01-15/2020-01-17" }, "partySize": 6, "staffFacilitators": [ { "name": "John Smith" } ], "location": { "zipCode": "94086", "city": "Sunnyvale", "postalAddress": { "regionCode": "US", "postalCode": "94086", "administrativeArea": "CA", "locality": "Sunnyvale", "addressLines": [ "222, Some other Street" ] } } } } ] }, "buyerInfo": { "email": "janedoe@gmail.com", "firstName": "Jane", "lastName": "Doe", "displayName": "Jane Doe" }, "followUpActions": [ { "type": "VIEW_DETAILS", "title": "View details", "openUrlAction": { "url": "https://example.com" } }, { "type": "CALL", "title": "Call us", "openUrlAction": { "url": "tel:+16501112222" } }, { "type": "EMAIL", "title": "Email us", "openUrlAction": { "url": "mailto:person@example.com" } } ], "termsOfServiceUrl": "https://www.example.com" } } } ] } ] }
ユーザーの決定に対応する
提案された注文にユーザーが応答すると、フルフィルメントは、TransactionDecisionValue
を含む引数を含む actions_intent_TRANSACTION_DECISION
インテントを受け取ります。この値には次のものが含まれます。
transactionDecision
- 注文の提案に関するユーザーの決定。指定できる値はORDER_ACCEPTED
、ORDER_REJECTED
、CART_CHANGE_REQUESTED
、USER_CANNOT_TRANSACT
です。
このリクエストを処理するには、actions_intent_TRANSACTION_DECISION
イベントによってトリガーされる Dialogflow インテントを宣言します。このインテントをフルフィルメントで処理します。
const arg = conv.arguments.get('TRANSACTION_DECISION_VALUE'); if (arg && arg.transactionDecision === 'ORDER_ACCEPTED') { console.log('order accepted'); const order = arg.order; }
Argument transactionDecisionValue = request .getArgument("TRANSACTION_DECISION_VALUE"); Map<String, Object> extension = null; if (transactionDecisionValue != null) { extension = transactionDecisionValue.getExtension(); } String transactionDecision = null; if (extension != null) { transactionDecision = (String) extension.get("transactionDecision"); } if ((transactionDecision != null && transactionDecision.equals("ORDER_ACCEPTED"))) { OrderV3 order = ((OrderV3) extension.get("order")); }
下記の JSON は Webhook リクエストを示します。
{ "responseId": "", "queryResult": { "queryText": "", "action": "", "parameters": {}, "allRequiredParamsPresent": true, "fulfillmentText": "", "fulfillmentMessages": [], "outputContexts": [], "intent": { "name": "reservation_get_transaction_decision_df", "displayName": "reservation_get_transaction_decision_df" }, "intentDetectionConfidence": 1, "diagnosticInfo": {}, "languageCode": "" }, "originalDetectIntentRequest": { "source": "google", "version": "2", "payload": { "isInSandbox": true, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "inputs": [ { "rawInputs": [], "intent": "", "arguments": [] } ], "user": {}, "conversation": {}, "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] } }, "session": "" }
下記の JSON は Webhook リクエストを示します。
{ "user": {}, "device": {}, "surface": { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] }, "conversation": {}, "inputs": [ { "rawInputs": [], "intent": "reservation_get_transaction_decision_asdk", "arguments": [] } ], "availableSurfaces": [ { "capabilities": [ { "name": "actions.capability.SCREEN_OUTPUT" }, { "name": "actions.capability.AUDIO_OUTPUT" }, { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" }, { "name": "actions.capability.WEB_BROWSER" } ] } ] }
4. 予約を確定して領収書を送る
actions.intent.TRANSACTION_DECISION
インテントの transactionDecision
が ORDER_ACCEPTED
の場合は、予約をスケジュールするために必要な処理(独自のデータベースで保持する場合など)を実行します。
シンプルなレスポンスを送信して会話を続けます。ユーザーには、レスポンスと一緒に「折りたたまれた領収書カード」が届きます。
フルフィルメント
// Set lastUpdateTime and update status of reservation order.lastUpdateTime = new Date().toISOString(); order.reservation.status = 'CONFIRMED'; order.reservation.userVisibleStatusLabel = 'Reservation confirmed'; order.reservation.confirmationCode = '123ABCDEFGXYZ'; // Send synchronous order update conv.ask(`Transaction completed! You're all set!`); conv.ask(new OrderUpdate({ type: 'SNAPSHOT', reason: 'Reason string', order: order, }));
ResponseBuilder responseBuilder = getResponseBuilder(request); order.setLastUpdateTime(Instant.now().toString()); // Set reservation status to confirmed and provide confirmation code LineItemV3 lineItem = order.getContents().getLineItems().get(0); ReservationItemExtension reservationItemExtension = lineItem.getReservation(); reservationItemExtension.setStatus("CONFIRMED"); reservationItemExtension.setUserVisibleStatusLabel("Reservation confirmed."); reservationItemExtension.setConfirmationCode("123ABCDEFGXYZ"); lineItem.setReservation(reservationItemExtension); order.getContents().getLineItems().set(0, lineItem); // Order update OrderUpdateV3 orderUpdate = new OrderUpdateV3() .setType("SNAPSHOT") .setReason("Reason string") .setOrder(order); responseBuilder .add("Transaction completed! You're all set! Would you like to do anything else?") .add(new StructuredResponse().setOrderUpdateV3(orderUpdate)); return responseBuilder.build();
下記の JSON は Webhook レスポンスを示します。
{ "payload": { "google": { "expectUserResponse": true, "richResponse": { "items": [ { "simpleResponse": { "textToSpeech": "Transaction completed! You're all set!" } }, { "structuredResponse": { "orderUpdateV3": { "type": "SNAPSHOT", "reason": "Reason string", "order": { "merchantOrderId": "UNIQUE_ORDER_ID", "reservation": { "status": "CONFIRMED", "userVisibleStatusLabel": "Reservation confirmed", "confirmationCode": "123ABCDEFGXYZ" }, "lastUpdateTime": "2019-07-17T18:25:30.187Z" } } } } ] } } } }
下記の JSON は Webhook レスポンスを示します。
{ "expectUserResponse": true, "expectedInputs": [ { "possibleIntents": [ { "intent": "actions.intent.TEXT" } ], "inputPrompt": { "richInitialPrompt": { "items": [ { "simpleResponse": { "textToSpeech": "Transaction completed! You're all set!" } }, { "structuredResponse": { "orderUpdateV3": { "type": "SNAPSHOT", "reason": "Reason string", "order": { "merchantOrderId": "UNIQUE_ORDER_ID", "reservation": { "status": "CONFIRMED", "userVisibleStatusLabel": "Reservation confirmed", "confirmationCode": "123ABCDEFGXYZ" }, "lastUpdateTime": "2019-07-17T18:25:30.059Z" } } } } ] } } } ] }
5. 注文の更新情報を送信する
予約ステータスは存続期間に応じて変化します。注文のステータスと詳細を含む HTTP PATCH リクエストを使用して、ユーザーの予約注文の更新を Orders API に送信します。
Orders API に非同期リクエストを設定する
Orders API に対する注文更新リクエストはアクセス トークンで承認されます。Orders API に注文の更新を PATCH するには、Actions Console プロジェクトに関連付けられた JSON サービス アカウント キーをダウンロードして、そのサービス アカウント キーを署名なしトークンと交換します。署名なしトークンは、HTTP リクエストの Authorization
ヘッダーに渡すことができます。
サービス アカウント キーを取得するには、次の手順に従います。
- Google Cloud コンソールで、メニューの 単に お知らせください 所有の可能です デフォルトの [API とサービス] > 認証情報 > 認証情報の作成 > サービス アカウント キー
- [サービス アカウント] で [新しいサービス アカウント] を選択します。
- サービス アカウントを
service-account
に設定します。 - [役割] を [プロジェクト] > [オーナー] に設定します。
- キータイプを [JSON] に設定します。
- [作成] を選択します。
- 非公開の JSON サービス アカウント キーがローカルマシンにダウンロードされます。
注文を更新するコードで、Google API クライアント ライブラリと "https://www.googleapis.com/auth/actions.order.developer" スコープを使用して、サービスキーを署名なしトークンと交換します。インストール手順と例については、API クライアント ライブラリの GitHub ページをご覧ください。
鍵交換の例については、Node.js と Java のサンプルで order-update.js
をご覧ください。
注文の更新情報を送信する
サービス アカウント キーを OAuth 署名なしトークンと交換したら、注文の更新を承認済みの PATCH リクエストとして Orders API に送信します。
Orders API の URL:
PATCH https://actions.googleapis.com/v3/orders/${orderId}
リクエストに次のヘッダーを指定します。
"Authorization: Bearer token"
は、サービス アカウント キーを交換した OAuth 署名なしトークンに置き換えます。"Content-Type: application/json"
.
PATCH リクエストは、次の形式の JSON 本文を使用します。
{ "orderUpdate": OrderUpdate }
OrderUpdate
オブジェクトは、次の最上位フィールドで構成されます。
updateMask
- 更新する注文のフィールド。予約ステータスを更新するには、値をreservation.status, reservation.userVisibleStatusLabel
に設定します。order
- 更新の内容。予約の内容を更新する場合は、更新されたOrder
オブジェクトに値を設定します。予約のステータスを更新するだけの場合(たとえば、"PENDING"
から"FULFILLED"
に)、オブジェクトには次のフィールドがあります。merchantOrderId
-Order
オブジェクトで設定したのと同じ ID。lastUpdateTime
- この更新のタイムスタンプ。purchase
- 次のものを含むオブジェクト。status
-ReservationStatus
での注文のステータス(「CONFIRMED
」や「CANCELLED
」など)。userVisibleStatusLabel
- 注文ステータスの詳細を示すユーザー向けのラベル。「予約が確定しました」など。
userNotification
オブジェクト。なお、このオブジェクトを含めても、ユーザーのデバイスに通知が表示されるとは限りません。
次のサンプルコードは、予約注文のステータスを FULFILLED
に更新する OrderUpdate
の例を示しています。
// Import the 'googleapis' module for authorizing the request. const {google} = require('googleapis'); // Import the 'request' module for sending an HTTP POST request. const request = require('request'); // Import the OrderUpdate class from the Actions on Google client library. const {OrderUpdate} = require('actions-on-google'); // Import the service account key used to authorize the request. Replace the string path with a path to your service account key. const key = require('./service-account.json'); // Create a new JWT client for the Actions API using credentials from the service account key. let jwtClient = new google.auth.JWT( key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/actions.order.developer'], null ); // Authorize the client asynchronously, passing in a callback to run upon authorization. jwtClient.authorize((err, tokens) => { if (err) { console.log(err); return; } // Declare the ID of the order to update. const orderId = '<UNIQUE_MERCHANT_ORDER_ID>'; const orderUpdateJson = new OrderUpdate({ updateMask: [ 'lastUpdateTime', 'contents.lineItems.reservation.status', 'contents.lineItems.reservation.userVisibleStatusLabel', ].join(','), order: { merchantOrderId: orderId, lastUpdateTime: new Date().toISOString(), contents: { lineItems: [ { reservation: { status: 'FULFILLED', userVisibleStatusLabel: 'Reservation fulfilled', }, } ] } }, reason: 'Reservation status was updated to fulfilled.', }); // Set up the PATCH request header and body, including the authorized token // and order update. const bearer = 'Bearer ' + tokens.access_token; const options = { method: 'PATCH', url: `https://actions.googleapis.com/v3/orders/${orderId}`, headers: { 'Authorization': bearer, }, body: { header: { 'isInSandbox': true, }, orderUpdate: orderUpdateJson, }, json: true, }; // Send the PATCH request to the Orders API. request.patch(options, (err, httpResponse, body) => { if (err) { console.log('There was an error...'); console.log(err); return; } }); });
// Create order update FieldMask fieldMask = FieldMask.newBuilder().addAllPaths(Arrays.asList( "lastUpdateTime", "contents.lineItems.reservation.status", "contents.lineItems.reservation.userVisibleStatusLabel")) .build(); OrderUpdateV3 orderUpdate = new OrderUpdateV3() .setOrder(new OrderV3() .setMerchantOrderId(orderId) .setLastUpdateTime(Instant.now().toString()) .setContents(new OrderContents() .setLineItems(Collections.singletonList(new LineItemV3() .setReservation(new ReservationItemExtension() .setStatus("FULFILLED") .setUserVisibleStatusLabel("Reservation fulfilled.")))))) .setUpdateMask(FieldMaskUtil.toString(fieldMask)) .setReason("Reservation status was updated to fulfilled."); // Setup JSON body containing order update JsonParser parser = new JsonParser(); JsonObject orderUpdateJson = parser.parse(new Gson().toJson(orderUpdate)).getAsJsonObject(); JsonObject body = new JsonObject(); body.add("orderUpdate", orderUpdateJson); JsonObject header = new JsonObject(); header.addProperty("isInSandbox", true); body.add("header", header); StringEntity entity = new StringEntity(body.toString()); entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); request.setEntity(entity); // Make request HttpClient httpClient = HttpClientBuilder.create().build(); HttpResponse response = httpClient.execute(request);
予約ステータスを設定する
注文更新の ReservationStatus
には、注文の現在の状態を記述する必要があります。アップデートの order.ReservationStatus
フィールドで、次のいずれかの値を使用します。
PENDING
- 予約はアクションによって作成されましたが、バックエンドで追加の処理が必要です。CONFIRMED
- 予約がスケジューリング バックエンドで確認されました。CANCELLED
- ユーザーが予約をキャンセルしました。FULFILLED
- ユーザーの予約がサービスによって履行されました。CHANGE_REQUESTED
- ユーザーが予約の変更をリクエストし、変更の処理中です。REJECTED
- 予約を処理できなかったか、確認できなかった場合。
予約に関連する各ステータスの注文の更新情報を送信します。たとえば、予約がリクエストされた後に確認するための手動処理が必要な場合は、その追加処理が完了するまで PENDING
注文の更新を送信します。すべての予約でステータス値が必要になるとは限りません。
トラブルシューティング
テスト中に問題が発生した場合は、取引に関するトラブルシューティングの手順をご覧ください。