使用 Google Pay 建立实体交易

本指南将逐步介绍 Action 项目的开发流程,该项目整合了实体商品的交易,并使用 Google Pay 进行付款。

交易流程

当您的 Actions 项目使用商家管理的付款处理实体交易时,它会使用以下流程:

  1. 收集信息(可选)- 根据事务的性质,您可能需要在对话开始时向用户收集以下信息:
    1. 验证交易要求 - 在对话开始时,验证用户是否符合进行交易的要求,例如在构建购物车之前正确配置和可用付款信息。
    2. 请求配送地址 - 如果交易需要配送地址,请从用户那里收集一个。
  2. 构建订单 - 引导用户完成“购物车组装”,让他们选择想要购买的商品。
  3. 建议订单 - 购物车完成后,向用户建议订单,以便他们确认订单是否正确。如果订单得到确认,您会收到包含订单详情和付款令牌的响应。
  4. 敲定订单并发送收据 - 确认订单后,更新商品目录跟踪或其他履单服务,然后向用户发送收据。
  5. 发送订单更新 - 在履单的整个生命周期内,通过向 Orders API 发送 PATCH 请求向用户提供订单更新。

限制和审核指南

请注意,有其他政策适用于“与交易相关的 Action”。审核涉及交易的 Action 最多可能需要 6 周时间,因此在规划发布时间表时请将该时间考虑在内。为了简化审核流程,请确保在提交 Action 以供审核之前遵守交易政策和准则

您只能在以下国家/地区部署销售实体商品的 Action:

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

构建您的项目

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

初始设置

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

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

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

1. 收集信息(可选)

1a. 验证交易要求(可选)

用户表明想要进行购买后,您应立即进行检查以确保他们能够进行交易。例如,调用时,您的 Action 可能会询问“您想要订购鞋子还是查看帐号余额?”如果用户说“订购鞋子”,您应该确保他们可以继续操作,并让用户有机会修正任何设置,使其无法继续进行交易。为此,您应该过渡到执行事务要求检查的场景。

创建事务要求检查场景
  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: >-
                You are ready to purchase physical goods.
    
  6. Transition 下,选择其他场景,允许用户继续对话并继续进行交易。

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

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

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

申请配送地址

如果您的交易需要用户的配送地址,您应向用户请求。这可能有助于确定总价、送货/自提位置,或确保用户在您的服务区域内。为此,您应该过渡到提示用户输入配送地址的场景。

创建配送地址场景

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

配置槽时,您可以提供一个 reason,用于在 Google 助理的请求前加上字符串来获取地址。默认原因字符串为“to known to how to send the order”。因此,Google 助理可能会询问用户:“为了让订单去哪里,我需要获取您的配送地址”。

  • 在有屏幕的 surface 上,用户可以选择希望使用哪个地址进行交易。如果他们之前没有提供地址,则可以输入新地址。
  • 在仅支持语音的 surface 上,Google 助理会要求用户提供分享交易的默认地址的权限。如果他们之前没有提供地址,则对话将移交给手机以供访问。

如需处理“配送地址”结果,请按以下步骤操作:

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

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

  5. 启用 Send prompts 并提供一条简单的提示,让用户知道您已收到他们的地址:

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Great! Your order will be delivered to
                $session.params.TransactionDeliveryAddress.location.postalAddress.locality
                $session.params.TransactionDeliveryAddress.location.postalAddress.administrativeArea
                $session.params.TransactionDeliveryAddress.location.postalAddress.regionCode
                $session.params.TransactionDeliveryAddress.location.postalAddress.postalCode
    
  6. Transition 下,选择其他场景,以便用户继续对话。

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

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

    candidates:
      - first_simple:
          variants:
            - speech: I failed to get your delivery address.
    
  9. Transition 下,如果用户无法进行交易,请选择 EndConversation 以结束对话。

构建订单

获得所需的用户信息后,您需要打造“购物车组装”体验来指导用户构建订单。每个 Action 的购物车组装流程都会因其产品或服务而略有不同。

最基本的购物车组装体验是让用户从列表中选择要添加到订单中的商品,但您可以设计对话以简化用户体验。您可以打造一种购物车组装体验,让用户能够通过简单的“是”或“否”问题重新订购最近购买的商品。您还可以向用户展示包含热门“精选”或“推荐”商品的轮播界面或列表卡片。

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

创建一个订单

在整个对话中,您需要收集用户想要购买的商品,然后构建 Order 对象。

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

  • buyerInfo - 进行购买的用户的相关信息。
  • transactionMerchant - 提供订单处理工具的商家的相关信息。
  • contents - 列为 lineItems 的订单的实际内容。
  • priceAttributes - 订单的价格详情,包括订单的总费用(含折扣和税费)。

如需构建购物车,请参阅 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: 'Pizza',
        description: 'A four cheese pizza.',
        priceAttributes: [
          {
            type: 'REGULAR',
            name: 'Item Price',
            state: 'ACTUAL',
            amount: {
              currencyCode: 'USD',
              amountInMicros: 8990000,
            },
            taxIncluded: true,
          },
          {
            type: 'TOTAL',
            name: 'Total Price',
            state: 'ACTUAL',
            amount: {
              currencyCode: 'USD',
              amountInMicros: 9990000,
            },
            taxIncluded: true,
          },
        ],
        notes: [
          'Extra cheese.',
        ],
        purchase: {
          quantity: 1,
          unitMeasure: {
            measure: 1,
            unit: 'POUND',
          },
          itemOptions: [
            {
              id: 'ITEM_OPTION_ID',
              name: 'Pepperoni',
              prices: [
                {
                  type: 'REGULAR',
                  state: 'ACTUAL',
                  name: 'Item Price',
                  amount: {
                    currencyCode: 'USD',
                    amountInMicros: 1000000,
                  },
                  taxIncluded: true,
                },
                {
                  type: 'TOTAL',
                  name: 'Total Price',
                  state: 'ACTUAL',
                  amount: {
                    currencyCode: 'USD',
                    amountInMicros: 1000000,
                  },
                  taxIncluded: true,
                },
              ],
              note: 'Extra pepperoni',
              quantity: 1,
              subOptions: [],
            },
          ],
        },
      },
    ],
  },
  buyerInfo: {
    email: 'janedoe@gmail.com',
    firstName: 'Jane',
    lastName: 'Doe',
    displayName: 'Jane Doe',
  },
  priceAttributes: [
    {
      type: 'SUBTOTAL',
      name: 'Subtotal',
      state: 'ESTIMATE',
      amount: {
        currencyCode: 'USD',
        amountInMicros: 9990000,
      },
      taxIncluded: true,
    },
    {
      type: 'DELIVERY',
      name: 'Delivery',
      state: 'ACTUAL',
      amount: {
        currencyCode: 'USD',
        amountInMicros: 2000000,
      },
      taxIncluded: true,
    },
    {
      type: 'TAX',
      name: 'Tax',
      state: 'ESTIMATE',
      amount: {
        currencyCode: 'USD',
        amountInMicros: 3780000,
      },
      taxIncluded: true,
    },
    {
      type: 'TOTAL',
      name: 'Total Price',
      state: 'ESTIMATE',
      amount: {
        currencyCode: 'USD',
        amountInMicros: 15770000,
      },
      taxIncluded: true,
    },
  ],
  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',
  note: 'Sale event',
  promotions: [
    {
      coupon: 'COUPON_CODE',
    },
  ],
  purchase: {
    status: 'CREATED',
    userVisibleStatusLabel: 'CREATED',
    type: 'FOOD',
    returnsInfo: {
      isReturnable: false,
      daysToReturn: 1,
      policyUrl: 'http://www.example.com',
    },
    fulfillmentInfo: {
      id: 'FULFILLMENT_SERVICE_ID',
      fulfillmentType: 'DELIVERY',
      expectedFulfillmentTime: {
        timeIso8601: '2019-09-25T18:00:00.877Z',
      },
      location: location,
      price: {
        type: 'REGULAR',
        name: 'Delivery Price',
        state: 'ACTUAL',
        amount: {
          currencyCode: 'USD',
          amountInMicros: 2000000,
        },
        taxIncluded: true,
      },
      fulfillmentContact: {
        email: 'johnjohnson@gmail.com',
        firstName: 'John',
        lastName: 'Johnson',
        displayName: 'John Johnson',
      },
    },
    purchaseLocationType: 'ONLINE_PURCHASE',
  },
};

创建顺序和展示选项

在用户确认订单之前,系统会向其显示建议的订单卡片。您可以通过设置各种顺序和呈现选项来自定义此卡片向用户显示的方式。

以下是下达需要配送地址(包括订单确认卡片中用户的电子邮件地址)的订单和呈现选项:

const orderOptions = {
      'requestDeliveryAddress': true,
      'userInfoOptions': {
        'userInfoProperties': ['EMAIL']
      }
    };

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

创建付款参数

您的 paymentParameters 对象将包含令牌化参数,这些参数会根据您计划使用的 Google Pay 处理方(例如 Stripe、Braintree、ACI 等)而发生变化。

const paymentParamenters = {
      'googlePaymentOption': {
        // facilitationSpec is expected to be a serialized JSON string
        'facilitationSpec': JSON.stringify({
          'apiVersion': 2,
          'apiVersionMinor': 0,
          'merchantInfo': {
            'merchantName': 'Example Merchant',
          },
          'allowedPaymentMethods': [
            {
              'type': 'CARD',
              'parameters': {
                'allowedAuthMethods': ['PAN_ONLY', 'CRYPTOGRAM_3DS'],
                'allowedCardNetworks': [
                  'AMEX', 'DISCOVER', 'JCB', 'MASTERCARD', 'VISA'],
              },
              'tokenizationSpecification': {
                'type': 'PAYMENT_GATEWAY',
                'parameters': {
                  'gateway': 'example',
                  'gatewayMerchantId': 'exampleGatewayMerchantId',
                },
              },
            },
          ],
          'transactionInfo': {
            'totalPriceStatus': 'FINAL',
            'totalPrice': '15.77',
            'currencyCode': 'USD',
          },
        }),
      },
    };

每个支付网关的 tokenizationSpecification 对象内容将有所不同。下表显示了每个网关使用的参数:

示例
"parameters": {
  "gateway": "example",
  "gatewayMerchantId": "exampleGatewayMerchantId"
}
ACI
"parameters": {
  "gateway": "aciworldwide",
  "gatewayMerchantId": "YOUR_ENTITY_ID"
}
AdYEN
"parameters": {
  "gateway": "adyen",
  "gatewayMerchantId": "YOUR_MERCHANT_ACCOUNT_NAME"
}
阿尔法银行
"parameters": {
  "gateway": "alfabank",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
BLUE_MEDIA
"parameters": {
  "gateway": "bluemedia",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
蓝色 NAP
"parameters": {
  "gateway": "bluesnap",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
巴西
"parameters": {
  "gateway": "braintree",
  "braintree:apiVersion": "v1",
  "braintree:sdkVersion": braintree.client.VERSION,
  "braintree:merchantId": "YOUR_BRAINTREE_MERCHANT_ID",
  "braintree:clientKey": "YOUR_BRAINTREE_TOKENIZATION_KEY"
}
CHASE_PAYMENTECH
"parameters": {
  "gateway": "chase",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ACCOUNT_NUMBER"
}
结账
"parameters": {
  "gateway": "checkoutltd",
  "gatewayMerchantId": "YOUR_PUBLIC_KEY"
}
CLOUDPAYMENTS
"parameters": {
  "gateway": "cloudpayments",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
CYBERSOURCE
"parameters": {
  "gateway": "cybersource",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
数据传输
"parameters": {
  "gateway": "datatrans",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
EBANX
"parameters": {
  "gateway": "ebanx",
  "gatewayMerchantId": "YOUR_PUBLIC_INTEGRATION_KEY"
}
FIRST_DATA
"parameters": {
  "gateway": "firstdata",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
GLOBAL_PAYMENTS
"parameters": {
  "gateway": "globalpayments",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
GoPAY
"parameters": {
  "gateway": "gopay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
HITRUST
"parameters": {
  "gateway": "hitrustpay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
解决方案
"parameters": {
  "gateway": "imsolutions",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
歌剧
"parameters": {
  "gateway": "lyra",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
MPGS
"parameters": {
  "gateway": "mpgs",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
MONEY_Mail_RU
"parameters": {
  "gateway": "moneymailru",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NEWEBPAY
"parameters": {
  "gateway": "newebpay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NEXI
"parameters": {
  "gateway": "nexi",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
NMI
"parameters": {
  "gateway": "creditcall",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PAYSAFE
"parameters": {
  "gateway": "paysafe",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
付费
"parameters": {
  "gateway": "payture",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PAYU
"parameters": {
  "gateway": "payu",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
PRZELEWY24
"parameters": {
  "gateway": "przelewy24",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
RBKMONEY
"parameters": {
  "gateway": "rbkmoney",
  "gatewayMerchantId": "YOUR_MERCHANT_ID"
}
SBERBANK(SBERBANK)
"parameters": {
  "gateway": "sberbank",
  "gatewayMerchantId": "YOUR_ORGANIZATION_NAME"
}
方形
"parameters": {
  "gateway": "square",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
词条
"parameters": {
  "gateway": "stripe",
  "stripe:version": "2018-10-31",
  "stripe:publishableKey": "YOUR_PUBLIC_STRIPE_KEY"
}
TAPPAY
"parameters": {
  "gateway": "tappay",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
TINKOFF
"parameters": {
  "gateway": "tinkoff",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
UNITELLER
"parameters": {
  "gateway": "uniteller",
  "gatewayMerchantId": "YOUR_GATEWAY_MERCHANT_ID"
}
VANTIV
"parameters": {
  "gateway": "vantiv",
  "vantiv:merchantPayPageId": "YOUR_PAY_PAGE_ID",
  "vantiv:merchantOrderId": "YOUR_ORDER_ID",
  "vantiv:merchantTransactionId": "YOUR_TRANSACTION_ID",
  "vantiv:merchantReportGroup": "*web"
}
WORLDPAY
"parameters": {
  "gateway": "worldpay",
  "gatewayMerchantId": "YOUR_WORLDPAY_MERCHANT_ID"
}
YANDEX
"parameters": {
  "gateway": "yandexcheckout",
  "gatewayMerchantId": "YOUR_SHOP_ID"
}

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

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

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

提出订单建议

创建订单后,您必须向用户显示其确认或拒绝。 为此,您应该过渡到执行事务决策的场景。

创建事务决策场景

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

为了尝试填充 TransactionDecisionValue 槽,Google 助理会启动一种内置体验,在该体验中,您传递的 Order 会直接呈现到“购物车预览卡片”上。用户可以说“下单”、拒绝交易、更改付款方式(如信用卡或地址)或请求更改订单内容。

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

处理事务决策结果

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

  • ORDER_ACCEPTED,
  • ORDER_REJECTED,
  • DELIVERY_ADDRESS_UPDATED,
  • 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 order
                $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: Look like you don't want to order anything. 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 下,如果用户无法进行交易,请选择 End sessions 以结束对话。

完成订单并发送收据

TransactionDecisionValue 槽返回 ORDER_ACCEPTED 的结果时,您必须立即执行所需的任何处理来“确认”订单(例如,将其保留在您自己的数据库中并向用户收费)。

您可以使用此响应结束对话,但必须包含简单回复才能使对话继续进行。在您提供此初始 orderUpdate 后,用户会看到“收起的收据卡”以及其余响应。此卡将反映用户在“订单记录”中找到的收据。

在订单确认过程中,您的订单对象可以包含 userVisibleOrderId,这是用户看到的订单 ID。您可以在此字段中重复使用 merchantOrderId

OrderUpdate 对象的部分内容需要包含一个后续操作对象,该对象以网址按钮的形式出现在订单详情底部,用户可在 Google 助理订单记录中找到这些按钮。

  • 您必须为每个订单至少提供一个 VIEW_DETAILS 后续 Action。其中应包含一个深层链接,指向您的移动应用或网站中的订单表示形式。
  • 除了 Action 对话中的收据卡之外,您还必须通过电子邮件发送符合交易的所有法律要求的正式收据。

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

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

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

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

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

发送订单动态

您需要在订单的整个生命周期内使用户随时了解订单状态。向 Orders API 发送包含订单状态和详细信息的 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 - 要更新的订单的字段。如需更新订单状态,请将值设置为 purchase.status, purchase.userVisibleStatusLabel
  • order - 更新的内容。如果要更新订单内容,请将值设置为更新后的 Order 对象。如果您要更新订单的状态(例如,从 "CONFIRMED" 更新为 "SHIPPED"),该对象包含以下字段:

    • merchantOrderId - 您在 Order 对象中设置的同一 ID。
    • lastUpdateTime - 此更新的时间戳。
    • purchase - 包含以下内容的对象:
      • status - 订单的状态,为 PurchaseStatus,例如“SHIPPED”或“DELIVERED”。
      • userVisibleStatusLabel - 一个面向用户的标签,提供订单状态的详细信息,例如“您的订单已发货,并在运输途中”。
  • userNotification 对象。请注意,添加此对象不能保证通知会显示在用户的设备上。

以下示例代码展示了一个将订单状态更新为 DELIVERED 的示例 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 order update
const orderUpdate = new OrderUpdate({
    updateMask: {
      paths: [
        'purchase.status',
        'purchase.user_visible_status_label'
      ]
    },
    order: {
      merchantOrderId: orderId, // Specify the ID of the order to update
      lastUpdateTime: new Date().toISOString(),
      purchase: {
        status: 'DELIVERED',
        userVisibleStatusLabel: 'Order delivered',
      },
    },
    reason: 'Order status updated to delivered.',
});

// 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}`);
}
设置购买状态

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

  • CREATED - 订单被用户接受并从 Action 的角度“创建”,但需要后端手动处理。
  • CONFIRMED - 订单处于有效状态,且正在接受履单。
  • IN_PREPARATION - 订单正在准备配送/配送,例如正在烹饪食物或商品正在包装。
  • READY_FOR_PICKUP - 订单商品可由收货人自提。
  • DELIVERED - 订单已送达收货人
  • OUT_OF_STOCK - 订单中的一件或多件商品缺货。
  • CHANGE_REQUESTED - 用户请求更改订单,并且更改正在处理中。
  • RETURNED - 订单商品送达后用户已退货。
  • REJECTED - 如果您无法处理订单、收取订单费用或以其他方式“激活”订单。
  • CANCELLED - 用户取消了订单。

您应该发送与交易相关的每个状态的订单更新。例如,如果您的交易需要手动处理才能在下单后记录订单,请发送 CREATED 订单更新,直到额外的处理完成为止。并非每个订单都需要每个状态值。

测试您的项目

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

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

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

问题排查

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