本指南將逐步說明使用 Orders API 開發 Actions 專案的程序。
交易流程
動作專案處理保留項目時,會使用下列流程:
- 驗證交易要求 (選用) - 在對話開始時使用交易需求輔助程式,確保使用者能夠進行交易。
- 建立訂單:逐步引導使用者完成「購物車組合」步驟,以便建立保留項目的詳細資料。
- 建議訂單 - 完成「購物車」後,請向使用者提出預訂「訂單」,以確認其正確。如果確認預訂,您會收到含有保留項目詳細資料的回應。
- 完成訂單並傳送收據:在訂單確認後,更新預訂系統並傳送收據給使用者。
- 傳送訂單更新 - 在預留的效期內,將 PATCH 要求傳送至 Orders API,進而掌握使用者保留項目狀態更新。
限制和查看規範
請注意,其他政策適用於使用交易和 Orders API 的動作。我們最多需要六週的處理時間來處理含有交易的動作,因此在規劃發布時間表時,請將這段時間納入考量。為簡化審查程序,在將動作送交審查之前,請務必遵守交易政策與規範。
您只能在下列國家/地區部署使用 Orders API 的動作:
澳洲 巴西 加拿大 印尼 |
日本 墨西哥 卡達 俄羅斯 |
新加坡 瑞士 泰國 土耳其 英國 美國 |
建構您的專案
如需完整的交易對話範例,請參閱 Node.js 和 Java 中的交易範例。
專案設定
建立動作時,您必須在動作控制台中指定要執行交易。此外,如果您使用的是 Node.JS 用戶端程式庫,請設定執行要求以使用最新版的 Orders API。
如要設定專案和執行要求,請按照以下步驟操作:
- 建立新專案或匯入現有專案。
- 依序前往「Deploy」(部署) >「Directory information」(目錄資訊)。
在「其他資訊」>「交易」下方,勾選「您的動作是否使用 Transactions API 執行實體商品交易?」方塊。
如果您是使用 Node.JS 用戶端程式庫建構動作的執行要求,請開啟執行要求程式碼並更新應用程式差異化功能,將
ordersv3
標記設為true
。下列程式碼片段是訂單第 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
意圖,確保他們能夠要求預留項目。例如,叫用時,您的動作可能會詢問「Do you want to keep a placet?」(您要預約座位?如果使用者回答「是」,則應立即要求此意圖。這樣就能確保他們可以繼續操作,並讓他們有機會修正所有設定,防止他們繼續交易。
要求交易要求檢查意圖會產生下列其中一個結果:
- 如果符合要求,執行要求會收到包含成功條件的意圖,然後您就可以繼續建立使用者的訂單。
如果有一或多項要求不符合要求,則執行要求會收到含有失敗條件的意圖。在此情況下,請結束對話或離開預訂流程。
如果使用者能夠修正錯誤,系統會自動提示使用者在裝置上解決這些問題。如果透過智慧音箱這類純語音介面進行對話,系統會將對話傳送到使用者的手機。
出貨
為確保使用者符合交易需求,請使用 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" } } ] } ] }
接收需求檢查結果
Google 助理執行意圖後,會傳送具有 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
意圖時,Google 助理會啟動內建體驗,其中 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" } } } ] } ] }
處理使用者的決定
使用者回應提議的訂單後,您的執行要求會收到 actions_intent_TRANSACTION_DECISION
意圖,其中含有一個包含 TransactionDecisionValue
的引數。這個值會包含以下內容:
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
意圖傳回 ORDER_ACCEPTED
的 transactionDecision
時,請執行任何必要的處理來排定預留項目,例如將預留項目保存在您自己的資料庫中。
傳送簡易回應,讓對話持續進行。使用者會收到「收合的收據卡」以及您的回覆。
出貨
// 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 更新訂單,請下載與 Actions Console 專案相關聯的 JSON 服務帳戶金鑰,然後交換服務帳戶金鑰,取得可傳遞至 HTTP 要求 Authorization
標頭的不記名憑證。
如要擷取服務帳戶金鑰,請執行下列步驟:
- 在 Google Cloud 控制台中,依序前往「選單」圖示 ⋮ >「API 和服務」>「憑證」>「建立憑證」>「服務帳戶金鑰」。
- 在「服務帳戶」下方,選取「新增服務帳戶」。
- 將服務帳戶設為
service-account
。 - 將「Role」(角色) 設為 [Project] (專案) > [Owner] (擁有者)。
- 將金鑰類型設為「JSON」。
- 選取「Create」(建立)。
- 系統會將私人 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 網址:
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
物件。請注意,加入這個物件並不保證會在使用者的裝置上收到通知。
以下程式碼範例顯示 OrderUpdate
範例,可將預訂訂單的狀態更新為 FULFILLED
:
// 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
訂單更新,直到該額外處理完成為止。並非所有保留項目都需要所有狀態值。
疑難排解
如果您在測試期間遇到任何問題,請參閱交易的疑難排解步驟。