构建预留

本指南将逐步介绍如何开发使用 Orders API 进行预订的 Actions 项目。

交易流程

当您的 Actions 项目处理预留时,它会使用以下流程:

  1. 验证事务要求(可选)- 在对话开始时使用事务要求帮助程序以确保用户能够执行事务。
  2. 构建订单 - 引导用户完成“购物车组件”,以便他们构建预订的详细信息。
  3. 建议订单 - 完成“购物车”后,向用户建议预留“订单”,以便他们可以确认其是否正确。如果预留得到确认,您会收到包含预留详情的响应。
  4. 完成订单并发送收据 - 确认订单后,更新您的预订系统并向用户发送收据。
  5. 发送订单更新 - 在预留的生命周期内,通过向 Orders API 发送 PATCH 请求向用户提供预留状态更新。

限制和审核指南

请注意,使用交易和 Orders API 的 Action 还需遵循其他政策。审核包含交易的 Action 最多可能需要 6 周时间,因此在规划发布时间表时请将该时间考虑在内。为了简化审核流程,请确保先遵守交易政策和准则,然后再提交 Action 以供审核。

您只能在以下国家/地区部署使用 Orders API 的 Action:

澳大利亚
巴西
加拿大
印度尼西亚
日本
墨西哥
卡塔尔
俄罗斯
新加坡
瑞士
泰国
土耳其
英国
美国

构建您的项目

如需查看事务性对话的详细示例,请参阅我们的 Node.js 中的事务示例

初始设置

创建 Action 时,您必须指定要在 Actions 控制台中执行事务。

如需设置项目和执行方式,请执行以下操作:

  1. 创建新项目或导入现有项目。
  2. 依次点击 Deploy > Directory information
  3. 其他信息 > 交易 > 下,选中显示“您的 Action 是否使用 Transaction API 执行实体商品的交易?”复选框。

验证交易要求(可选)

用户表明想要设置预留后,您应立即检查他们是否能够请求预留。例如,调用时,您的 Action 可能会询问“Would you like to booking a seat?”如果用户回答“是”,您应该确保他们可以继续操作,并让用户有机会修正任何导致他们无法继续进行交易的设置。为此,您应该过渡到执行事务要求检查的场景。

创建事务要求检查场景

  1. Scenes 标签页中,添加一个名为 TransactionRequirementsCheck 的新场景。
  2. 槽填充下,点击 + 以添加新槽。
  3. 选择类型下,选择 actions.type.TransactionRequirementsCheckResult 作为槽类型。
  4. 在槽名称字段中,将槽命名为 TransactionRequirementsCheck
  5. 选中自定义槽值回写复选框(默认处于启用状态)。
  6. 点击保存

事务要求检查会产生以下结果之一:

  • 如果满足这些要求,系统就会为会话参数设置成功条件,然后您可以继续构建用户订单。
  • 如果无法满足一项或多项要求,则会话参数会设为失败条件。在这种情况下,您应该从事务体验转向对话,或结束对话。
    • 如果导致失败状态的任何错误可由用户修复,系统会提示他们在设备上解决这些问题。如果对话是在仅支持语音的界面上进行,系统将启动移交到用户的手机上。

处理事务要求检查结果

  1. Scenes 标签页中,选择您新创建的 TransactionRequirementsCheck 场景。
  2. 条件下方,点击 + 以添加新条件。
  3. 在文本字段中,输入以下条件语法以检查成功条件:

    scene.slots.status == "FINAL" && session.params.TransactionRequirementsCheck.resultType == "CAN_TRANSACT"
    
  4. 将光标悬停在您刚刚添加的条件上,然后点击向上箭头,将其置于 if scene.slots.status == "FINAL" 前面。

  5. 启用 Send prompts 并提供一个简单的提示,让用户知道他们已准备好进行事务:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Looks like you're good to go!.
    
  6. Transition 下,选择其他场景,允许用户继续对话并继续进行交易。

  7. 选择条件 else if scene.slots.status == "FINAL"

  8. 启用 Send prompts 并提供一个简单的提示,让用户知道他们无法进行事务:

    candidates:
      - first_simple:
          variants:
            - speech: Transaction requirements check failed.
    
  9. Transition 下,如果用户无法进行交易,请选择 End sessions 以结束对话。

构建订单

获得所需的用户信息后,打造“购物车组合”体验,引导用户构建预订。每个 Action 的购物车组装流程都会因服务而异。

在基本的购物车组装体验中,用户可从列表中选择要添加到预订中的选项,但您也可以设计对话以简化用户体验。例如,打造一种购物车组装体验,让用户能够通过简单的是非问题来安排每月预订。您还可以向用户显示“推荐”预留的轮播界面或列表卡片。

我们建议使用富响应来直观呈现用户的选项,但也要设计对话,使用户只需通过语音就能构建自己的购物车。如需了解购物车组装体验的一些最佳实践和示例,请参阅设计准则

创建一个订单

在整个对话中,收集用户的预留详情,然后构建一个 Order 对象。

您的 Order 必须至少包含以下内容:

  • buyerInfo - 进行购买的用户的相关信息。
  • transactionMerchant - 提供订单处理工具的商家的相关信息。
  • contents - 列为 lineItems 的订单的实际内容。

如需构建购物车,请参阅 Order 响应文档。请注意,根据预留,您可能需要包含不同的字段。

以下示例代码显示了一个完整的预留订单,其中包括可选字段:

const order = {
   createTime: '2019-09-24T18:00:00.877Z',
   lastUpdateTime: '2019-09-24T18:00:00.877Z',
   merchantOrderId: orderId, // A unique ID String for the order
   userVisibleOrderId: orderId,
   transactionMerchant: {
     id: 'http://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: 'http://example.com',
       },
     },
     {
       type: 'CALL',
       title: 'Call us',
       openUrlAction: {
         url: 'tel:+16501112222',
       },
     },
     {
       type: 'EMAIL',
       title: 'Email us',
       openUrlAction: {
         url: 'mailto:person@example.com',
       },
     },
   ],
   termsOfServiceUrl: 'http://www.example.com'
 };

创建顺序和展示选项

const orderOptions = {
      'requestDeliveryAddress': false,
    };

const presentationOptions = {
      'actionDisplayName': 'RESERVE'
    };

将订单数据保存在会话参数中

在履单中,将订单数据保存到会话参数。订单对象将在同一会话的场景中使用。

conv.session.params.order = {
    '@type': 'type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec',
    order: order,
    orderOptions: orderOptions,
    presentationOptions: presentationOptions
};

提出订单建议

创建预留订单后,您必须将其呈现给用户,以进行确认或拒绝。为此,您应该过渡到执行事务决策的场景。

创建事务决策场景

  1. Scenes 标签页中,添加一个名为 TransactionDecision 的新场景。
  2. 槽填充下,点击 + 以添加新槽。
  3. Select type 下,选择 actions.type.TransactionDecisionValue 作为槽类型。
  4. 在槽名称字段中,将槽命名为 TransactionDecision
  5. 选中自定义槽值回写复选框(默认处于启用状态)。
  6. 配置槽下,从下拉菜单中选择使用会话参数
  7. 配置槽下,将用于存储订单的会话参数的名称输入到文本字段中(例如 $session.params.order)。
  8. 点击保存

为了尝试填充 TransactionDecisionValue 槽,Google 助理会启动一种内置体验,在该体验中,您传递的 Order 会直接呈现到“购物车预览卡片”上。用户可以说“安排预订”、拒绝交易或请求更改预留详细信息。

此时,用户也可以请求更改订单。在这种情况下,您应确保您的执行方式可以在完成购物车组装体验后处理订单更改请求。

处理事务决策结果

TransactionDecisionValue 槽被填充时,用户对交易决策的回答将存储在会话参数中。此值包含以下内容:

  • ORDER_ACCEPTED,
  • ORDER_REJECTED,
  • CART_CHANGE_REQUESTED
  • USER_CANNOT_TRANSACT.

如需处理事务决策结果,请执行以下操作:

  1. Scenes 标签页中,选择您新创建的 TransactionDecision 场景。
  2. 条件下方,点击 + 以添加新条件。
  3. 在文本字段中,输入以下条件语法以检查成功条件:

    scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_ACCEPTED"
    
  4. 将光标悬停在您刚刚添加的条件上,然后点击向上箭头,将其放在 if scene.slots.status == "FINAL" 前面。

  5. 启用 Send prompts 并提供一个简单的提示,让用户知道其预留已完成:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Transaction completed! Your reservation
                $session.params.TransactionDecision.order.merchantOrderId is all
                set!
    
  6. Transition 下,选择 End sessions 以结束对话。

  7. 条件下方,点击 + 以添加新条件。

  8. 在文本字段中,输入以下条件语法以检查失败条件:

      scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_REJECTED"
    
  9. 将光标悬停在您刚刚添加的条件上,然后点击向上箭头,将其放在 if scene.slots.status == "FINAL" 前面。

  10. 启用发送提示并提供简单的提示,告知用户订单已被拒绝:

    candidates:
      - first_simple:
          variants:
            - speech: Looks like you don't want to set up a reservation. Goodbye.
    
  11. Transition 下,选择 End sessions 以结束对话。

  12. 选择条件 else if scene.slots.status == "FINAL"

  13. 启用 Send prompts 并提供一个简单的提示,让用户知道他们无法进行事务:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Transaction failed with status
                $session.params.TransactionDecision.transactionDecision
    
  14. Transition 下,如果用户无法进行交易,请选择 EndConversation 以结束对话。

完成预订并发送收据

TransactionDecisionValue 槽返回 ORDER_ACCEPTED 的结果时,您必须立即执行安排预留所需的任何处理(例如将其保留在您自己的数据库中)。

发送简单回复,让对话继续下去。用户会在响应时收到“收起的收据卡片”。

如需发送初始订单更新,请执行以下操作:

  1. Scenes 标签页中,选择您的 TransactionDecision 场景。
  2. 条件下,选择用于检查成功结果的条件 ORDER_ACCEPTED

    scene.slots.status == "FINAL" && session.params.TransactionDecision.transactionDecision == "ORDER_ACCEPTED"
    
  3. 对于此条件,请启用调用 webhook,并提供 intent 处理程序名称,例如 update_order

  4. 在您的 webhook 代码中,添加一个用于发送初始订单更新的 intent 处理程序:

    app.handle('update_order', conv => {
      const currentTime = new Date().toISOString();
      let order = conv.session.params.TransactionDecision.order;
      conv.add(new OrderUpdate({
        'updateMask': {
          'paths': [
            'reservation.status',
            'reservation.user_visible_status_label',
            'reservation.confirmation_code'
          ]
        },
        'order': {
          'merchantOrderId': order.merchantOrderId,
          'lastUpdateTime': currentTime,
          'reservation': {
            'status': 'CONFIRMED',
            'userVisibleStatusLabel': 'Reservation confirmed',
            'confirmationCode': '123ABCDEFGXYZ',
          },
        },
        'reason': 'Reason string'
      }));
    });
    

发送订单动态

预留状态会在其生命周期内发生变化。通过 HTTP PATCH 请求将用户预订订单更新包含订单状态和详细信息。

设置向 Orders API 发送的异步请求

对 Orders API 的订单更新请求由访问令牌进行授权。如需修补 Orders API 的订单更新,请下载与您的 Actions 控制台项目关联的 JSON 服务帐号密钥,然后用服务帐号密钥交换可以传递到 HTTP 请求的 Authorization 标头的不记名令牌。

如需检索您的服务帐号密钥,请执行以下步骤:

  1. Google Cloud 控制台中,依次点击“菜单”图标 ☰ >“API 和服务”>“凭据”>“创建凭据”>“服务帐号密钥”
  2. 服务帐号下,选择新的服务帐号
  3. 将服务帐号设置为 service-account
  4. 角色设置为项目 > Owner
  5. 将密钥类型设置为 JSON
  6. 选择创建
  7. 一个 JSON 私钥服务账号密钥将下载到您的本地机器。

在订单更新代码中,请使用 Google API 客户端库和 "https://www.googleapis.com/auth/actions.order.developer" 范围将服务密钥交换为不记名令牌。您可以在 API 客户端库的 GitHub 页面上找到安装步骤和示例。

如需查看密钥交换示例,请参考我们的 Node.js 示例中的 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 对象。请注意,添加此对象不能保证通知会显示在用户的设备上。

以下示例代码展示了一个将预留订单状态更新为 FULFILLED 的示例 OrderUpdate

// Import the 'googleapis' module for authorizing the request.
const {google} = require('googleapis');
// Import the 'request-promise' module for sending an HTTP POST request.
const request = require('request-promise');
// Import the OrderUpdate class from the client library.
const {OrderUpdate} = require('@assistant/conversation');

// Import the service account key used to authorize the request.
// Replacing the string path with a path to your service account key.
// i.e. const serviceAccountKey = 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(
   serviceAccountKey.client_email,
   null,
   serviceAccountKey.private_key,
   ['https://www.googleapis.com/auth/actions.order.developer'],
   null,
);

// Authorize the client
let tokens = await jwtClient.authorize();

// Declare the ID of the order to update.
const orderId = '<UNIQUE_MERCHANT_ORDER_ID>';

// Declare order update
const orderUpdate = new OrderUpdate({
   updateMask: {
     paths: [
       'contents.lineItems.reservation.status',
       'contents.lineItems.reservation.userVisibleStatusLabel'
     ]
   },
   order: {
     merchantOrderId: orderId, // Specify the ID of the order to update
     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.
let options = {
 method: 'PATCH',
 uri: `https://actions.googleapis.com/v3/orders/${orderId}`,
 auth: {
   bearer: tokens.access_token,
 },
 body: {
   header: {
     isInSandbox: true,
   },
   orderUpdate,
 },
 json: true,
};

// Send the PATCH request to the Orders API.
try {
 await request(options);
} catch (e) {
 console.log(`Error: ${e}`);
}

设置预留状态

订单更新的 ReservationStatus 必须描述订单的当前状态。在更新的 order.ReservationStatus 字段中,使用以下某个值:

  • PENDING - 预留已由您的 Action“创建”,但需要在您的后端进行额外处理。
  • CONFIRMED - 已在日程安排后端确认预订。
  • CANCELLED - 用户取消了预订。
  • FULFILLED - 服务实现了用户的预订。
  • CHANGE_REQUESTED - 用户请求了对预留的更改,并且更改正在处理中。
  • REJECTED - 如果您无法处理或以其他方式确认预留。

针对与预留相关的每种状态发送订单更新。例如,如果您的预留在收到预留请求后需要手动处理来确认,请发送 PENDING 订单更新,直到额外处理完成为止。并非每个预留都需要每个状态值。

测试您的项目

测试项目时,您可以在 Actions 控制台中启用沙盒模式,以便在不通过付款方式扣款的情况下测试您的 Action。如需启用沙盒模式,请按以下步骤操作:

  1. 在 Actions 控制台中,点击导航栏中的 Test
  2. 点击设置
  3. 启用 Development Sandbox 选项。

对于物理交易,您还可以在示例中将字段 isInSandbox 设置为 true。此操作等同于在 Actions 控制台中启用沙盒模式设置。如需查看使用 isInSandbox 的代码段,请参阅发送订单更新部分。

问题排查

如果您在测试期间遇到任何问题,请阅读我们针对事务的问题排查步骤