В этом руководстве описан процесс разработки проекта Actions, в котором для бронирования используется Orders API.
Процесс транзакции
Когда ваш проект Actions обрабатывает бронирование, он использует следующий поток:
- Проверка требований к транзакциям (необязательно). Используйте помощник по требованиям к транзакциям в начале разговора, чтобы убедиться, что пользователь может выполнить транзакцию.
- Оформление заказа. Пользователь добавляет в корзину нужные услуги и указывает детали бронирования.
- Предложите заказ. Когда корзина будет заполнена, предложите пользователю зарезервировать заказ, чтобы он мог подтвердить, что все верно. Если бронирование подтверждено, вы получите ответ со сведениями о бронировании.
- Завершение заказа и отправка чека. После подтверждения заказа обновите систему бронирования и отправьте пользователю чек.
- Отправка обновлений заказа. В течение всего срока действия бронирования отправляйте пользователю обновления статуса бронирования, отправляя запросы PATCH в Orders API.
Ограничения и правила проверки
Обратите внимание, что к действиям, в которых используются API транзакций и заказов, применяются дополнительные правила. Проверка действий с транзакциями может занять до шести недель, поэтому учитывайте это при планировании графика выпуска. Чтобы упростить процесс проверки, убедитесь, что вы соблюдаете правила и рекомендации для транзакций, прежде чем отправлять действие на проверку.
Развертывать действия, использующие Orders API, можно только в следующих странах:
|
Австралия Бразилия Индонезия Канада |
Япония Мексика Катар Россия |
Сингапур США Таиланд Турция Швейцария |
Создайте проект
Примеры диалогов, связанных с транзакциями, можно найти в примерах транзакций на Node.js и Java.
Настройка проекта
При создании действия необходимо указать в консоли Actions, что вы хотите выполнять транзакции. Если вы используете клиентскую библиотеку Node.JS, настройте фулфилмент так, чтобы он работал с последней версией Orders API.
Чтобы настроить проект и выполнение, сделайте следующее:
- Создайте новый проект или импортируйте существующий.
- Перейдите в раздел Deploy (Развертывание) > Directory information (Информация в каталоге).
В разделе Дополнительная информация > Транзакции установите флажок "Используют ли ваши действия Transactions API для продажи физических товаров?".
Если вы используете клиентскую библиотеку Node.JS для создания фулфилмента Action, откройте код фулфилмента и обновите объявление приложения, чтобы установить для флага
ordersv3значениеtrue. В приведенном ниже фрагменте кода показан пример объявления приложения для Orders API версии 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, чтобы он мог отправить запрос на бронирование. Например, при вызове ваше действие может спросить: "Хотите забронировать место?" Если пользователь ответит утвердительно, сразу же запросите это намерение. Это позволит им продолжить и даст возможность исправить любые настройки, мешающие им продолжить транзакцию.
Запрос намерения проверки требований к транзакциям приводит к одному из следующих результатов:
- Если требования соблюдены, ваше выполнение получает намерение с условием успеха, и вы можете продолжить создание заказа пользователя.
Если одно или несколько требований не выполняются, ваше выполнение получает намерение с условием сбоя. В этом случае завершите разговор или перейдите к другой теме.
Если пользователь может устранить ошибку, ему автоматически предлагается сделать это на устройстве. Если разговор происходит на устройстве, которое поддерживает только голосовое взаимодействие, например на умной колонке, он перенаправляется на телефон пользователя.
выполнение заказов;
Чтобы убедиться, что пользователь соответствует требованиям к транзакциям, запросите выполнение намерения actions.intent.TRANSACTION_REQUIREMENTS_CHECK с объектом TransactionRequirementsCheckSpec.
Требования
Проверьте, соответствует ли пользователь требованиям к резервированию, с помощью клиентской библиотеки:
conv.ask(new TransactionRequirements());
return getResponseBuilder(request)
.add("Placeholder for transaction requirements text")
.add(new TransactionRequirements())
.build();Обратите внимание, что приведенный ниже код JSON описывает ответ вебхука.
{ "payload": { "google": { "expectUserResponse": true, "systemIntent": { "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", "data": { "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec" } } } } }
Обратите внимание, что приведенный ниже код JSON описывает ответ вебхука.
{ "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 и результатом проверки.
Чтобы правильно обработать этот запрос, объявите намерение Dialogflow, которое активируется событием actions_intent_TRANSACTION_REQUIREMENTS_CHECK. Когда намерение активируется, обработайте его в своем ответе:
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 описывает запрос вебхука.
{ "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 описывает запрос вебхука.
{ "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 описывает ответ вебхука.
{ "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 создайте объект TransactionDecision, содержащий Order и orderOptions.
Ниже приведен пример кода 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 описывает ответ вебхука.
{ "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 описывает ответ вебхука.
{ "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.
Чтобы обработать этот запрос, объявите намерение Dialogflow, которое запускается событием actions_intent_TRANSACTION_DECISION. Обработайте это намерение в своем фулфилменте:
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 описывает запрос вебхука.
{ "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 описывает запрос вебхука.
{
"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 описывает ответ вебхука.
{ "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 описывает ответ вебхука.
{ "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, авторизуются с помощью токена доступа. Чтобы отправить запрос PATCH в Orders API, скачайте ключ сервисного аккаунта JSON, связанный с вашим проектом в консоли Actions, а затем обменяйте его на токен на предъявителя, который можно передать в заголовке Authorization HTTP-запроса.
Чтобы получить ключ сервисного аккаунта, выполните следующие действия:
- В консоли Google Cloud выберите Меню ☰ > APIs & Services (API и сервисы) > Credentials (Учетные данные) > Create credentials (Создать учетные данные) > Service account key (Ключ сервисного аккаунта).
- В разделе Service Account (Сервисный аккаунт) выберите New Service Account (Новый сервисный аккаунт).
- Установите для сервисного аккаунта значение
service-account. - В поле Role (Роль) выберите Project (Проект) > Owner (Владелец).
- В качестве типа ключа выберите JSON.
- Нажмите Создать.
- Закрытый ключ сервисного аккаунта в формате JSON будет скачан на ваш компьютер.
В коде обновления заказа обменяйте ключ сервиса на токен на предъявителя, используя клиентскую библиотеку API Google и область действия "https://www.googleapis.com/auth/actions.order.developer". Инструкции по установке и примеры можно найти на странице клиентской библиотеки API в GitHub.
Пример обмена ключами можно найти в разделе order-update.js в наших примерах для Node.js и Java.
Отправлять уведомления о заказах
После того как вы обменяете ключ сервисного аккаунта на токен на предъявителя OAuth, отправьте обновления заказов в виде авторизованных запросов PATCH в Orders API.
URL 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.lastUpdateTime– временная метка обновления.purchase– объект, содержащий следующие элементы:status– статус заказа в видеReservationStatus, например "CONFIRMED" или "CANCELLED".userVisibleStatusLabel– пользовательский ярлык с информацией о состоянии заказа, например "Ваше бронирование подтверждено".
userNotification(необязательно) – объект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, пока эта обработка не будет завершена. Не для каждого бронирования требуются все значения статуса.
Устранение неполадок
Если во время тестирования возникнут проблемы, ознакомьтесь с инструкциями по устранению неполадок.