Создание резервирований (Dialogflow)

Это руководство проведет вас через процесс разработки проекта Actions, который использует API заказов для размещения бронирования.

Поток транзакций

Когда ваш проект Actions обрабатывает резервирование, он использует следующий процесс:

  1. Проверка требований к транзакциям (необязательно) . Используйте помощник по требованиям к транзакциям в начале разговора, чтобы убедиться, что пользователь способен выполнить транзакцию.
  2. Создайте заказ . Проведите пользователя через «сборку корзины», где он формирует детали своего бронирования.
  3. Предложить заказ . Как только «корзина» будет заполнена, предложите пользователю резервирование «заказа», чтобы он мог подтвердить его правильность. Если бронирование будет подтверждено, вы получите ответ с деталями бронирования.
  4. Завершите заказ и отправьте квитанцию . После подтверждения заказа обновите свою систему бронирования и отправьте пользователю квитанцию.
  5. Отправлять обновления заказов . В течение срока действия резервирования предоставляйте пользователю обновления статуса резервирования, отправляя запросы PATCH в API заказов.

Ограничения и рекомендации по проверке

Имейте в виду, что к Действиям, использующим транзакции и API заказов, применяются дополнительные политики. Проверка действий с транзакциями может занять до шести недель, поэтому учтите это время при планировании графика выпуска. Чтобы облегчить процесс проверки, убедитесь, что вы соблюдаете правила и рекомендации для транзакций, прежде чем отправлять свое действие на проверку.

Вы можете развертывать Действия, использующие Orders API, только в следующих странах:

Австралия
Бразилия
Канада
Индонезия
Япония
Мексика
Катар
Россия
Сингапур
Швейцария
Таиланд
Турция
Великобритания
Соединенные Штаты

Создайте свой проект

Подробные примеры транзакционных диалогов можно найти в наших примерах транзакций в Node.js и Java .

Настройка проекта

При создании своего Действия вы должны указать, что хотите выполнять транзакции в консоли Действия . Кроме того, если вы используете клиентскую библиотеку Node.JS, настройте выполнение так, чтобы использовать последнюю версию API заказов.

Чтобы настроить проект и выполнить его, выполните следующие действия:

  1. Создайте новый проект или импортируйте существующий проект.
  2. Перейдите к «Развертывание» > «Информация о каталоге» .
  3. В разделе «Дополнительная информация» > «Транзакции» > установите флажок «Используют ли ваши действия API транзакций для выполнения транзакций с физическими товарами?».

  4. Если вы используете клиентскую библиотеку 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 , чтобы гарантировать, что он сможет запросить резервирование. Например, при вызове ваше Действие может спросить: «Хотите ли вы зарезервировать место?» Если пользователь говорит «да», вам следует немедленно запросить это намерение. Это гарантирует, что они смогут продолжить работу, и даст им возможность исправить любые настройки, мешающие им продолжить транзакцию.

Запрос намерения проверить требования транзакций приводит к одному из следующих результатов:

  • Если требования соблюдены, ваше выполнение получает намерение с условием успеха, и вы можете приступить к формированию заказа пользователя.
  • Если одно или несколько требований не выполняются, ваше выполнение получает намерение с условием сбоя. В этом случае завершите разговор или отойдите от процесса резервирования.

    Если пользователь может исправить ошибку, ему автоматически будет предложено решить эти проблемы на своем устройстве. Если разговор происходит на поверхности, предназначенной только для голоса, например, на умной колонке, он передается на телефон пользователя.

Выполнение

Чтобы убедиться, что пользователь соответствует требованиям транзакции, запросите выполнение намерения actions.intent.TRANSACTION_REQUIREMENTS_CHECK с помощью объекта TransactionRequirementsCheckSpec .

Проверьте требования

Проверьте, удовлетворяет ли пользователь требованиям резервирования клиентской библиотеки:

Node.js
conv.ask(new TransactionRequirements());
Ява
return getResponseBuilder(request)
    .add("Placeholder for transaction requirements text")
    .add(new TransactionRequirements())
    .build();
Диалоговый поток JSON

Обратите внимание, что приведенный ниже JSON описывает ответ веб-перехватчика.

{
  "payload": {
    "google": {
      "expectUserResponse": true,
      "systemIntent": {
        "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK",
        "data": {
          "@type": "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec"
        }
      }
    }
  }
}
Действия SDK JSON

Обратите внимание, что приведенный ниже 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 . При срабатывании обработайте это намерение в ходе выполнения:

Node.js
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

Обратите внимание, что приведенный ниже 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": ""
}
Действия SDK JSON

Обратите внимание, что приведенный ниже 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 , чтобы создать корзину. Обратите внимание, что вам может потребоваться включить разные поля в зависимости от бронирования.

В приведенном ниже примере кода показан полный заказ на резервирование, включая необязательные поля:

Node.js
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

Обратите внимание, что приведенный ниже 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 для заказа:

Node.js
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

Обратите внимание, что приведенный ниже 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"
          }
        }
      }
    }
  }
}
Действия SDK JSON

Обратите внимание, что приведенный ниже 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 . Управляйте этим намерением в своем исполнении:

Node.js
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

Обратите внимание, что приведенный ниже 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": ""
}
Действия SDK JSON

Обратите внимание, что приведенный ниже 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 , выполните любую обработку, необходимую для планирования резервирования (например, сохранение ее в вашей собственной базе данных).

Отправьте простой ответ , чтобы поддержать разговор. Вместе с вашим ответом пользователь получает «свернутую карточку квитанции».

Выполнение

Node.js
// 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

Обратите внимание, что приведенный ниже 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"
                }
              }
            }
          }
        ]
      }
    }
  }
}
Действия SDK JSON

Обратите внимание, что приведенный ниже 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 в API заказов, содержащих статус заказа и подробную информацию.

Настройте асинхронные запросы к API заказов.

Запросы на обновление заказов к Orders API авторизуются токеном доступа. Чтобы добавить обновление заказа в Orders API, загрузите ключ учетной записи службы JSON, связанный с вашим проектом консоли действий, а затем замените ключ учетной записи службы на токен носителя, который можно передать в заголовок Authorization HTTP-запроса.

Чтобы получить ключ сервисной учетной записи, выполните следующие действия:

  1. В консоли Google Cloud перейдите в Меню ☰ > API и службы > Учетные данные > Создать учетные данные > Ключ учетной записи службы .
  2. В разделе «Учетная запись службы» выберите «Новая учетная запись службы» .
  3. Установите учетную запись службы на service-account .
  4. Установите роль « Проект» > «Владелец» .
  5. Установите тип ключа JSON .
  6. Выберите Создать .
  7. Ключ частной учетной записи службы JSON будет загружен на ваш локальный компьютер.

В коде обновления заказа замените свой сервисный ключ на токен на предъявителя, используя клиентскую библиотеку Google API и область действия «https://www.googleapis.com/auth/actions.order.developer» . Шаги и примеры установки вы можете найти на странице API клиентской библиотеки GitHub .

Ссылка на order-update.js в наших примерах Node.js и Java для примера обмена ключами.

Отправлять обновления заказов

После того как вы заменили ключ своей сервисной учетной записи на токен носителя OAuth, отправьте обновления заказов в виде авторизованных запросов PATCH в Orders API.

URL-адрес 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 :

Node.js
// 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 до тех пор, пока не будет выполнена дополнительная обработка. Не каждое резервирование требует каждого значения статуса.

Поиск неисправностей

Если во время тестирования у вас возникнут какие-либо проблемы, прочтите наши инструкции по устранению неполадок для транзакций.