构建消耗型数字交易

本指南介绍如何在对话型 Action 中添加数字交易,以便用户可以购买您的消耗型数字商品。

关键术语:消耗型数字商品是一种可供用户多次使用和购买多次的库存单位 (SKU),例如某款 Android 游戏的代币数量。此类数字商品与用户只能购买一次的非消耗型数字商品不同。

如需详细了解消耗型一次性商品,请参阅有关一次性商品专用功能的 Android 文档。

交易流程

本指南将简要介绍数字商品交易流程中发生的每个开发步骤。您的 Action 处理数字商品交易时,会使用以下流程:

  1. 设置 Digital Purchases API 客户端:您的 Action 使用 digital Purchases API 与 Google Play 商品目录进行通信并交易。在您的 Action 执行任何其他操作之前,它会创建一个使用服务密钥的 JWT 客户端,以便与 Digital Purchases API 进行通信。
  2. 收集信息:您的 Action 会收集有关用户和 Google Play 商品目录的基本信息,以便为交易做准备。
    1. 验证交易要求:您的 Action 会在购买流程开始时使用数字交易要求帮助程序来确保用户可以进行交易。
    2. 收集可用的商品目录:您的 Action 会检查 Google Play 商品目录并确定目前可供购买的商品。
  3. 构建订单:您的 Action 会向用户显示可用的数字商品,以便用户选择一件商品进行购买。
  4. 完成购买:您的 Action 会使用数字购物 API 根据用户在 Google Play 商店中的选择发起购买交易。
  5. 处理结果:您的 Action 会收到交易的状态代码,并通知用户购买成功(或采取其他步骤)。
  6. 使购买交易可重复:您的 Action 使用 digital purchase API 来“消耗”已购买的商品,使该商品可供该用户再次购买。

限制和审核指南

其他政策适用于“包含交易的 Action”。审核包含交易的 Action 可能需要几周时间,因此在规划发布时间表时请将这段时间考虑在内。为了简化审核流程,请确保先遵守交易政策和准则,然后再提交 Action 以供审核。

销售数字商品的 Action 只能在以下国家/地区部署:

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

前提条件

在将数字交易整合到 Action 中之前,您需要满足以下前提条件:

  • Google Play 上的开发者帐号商家帐号,用于在 Google Play 管理中心管理您的数字商品。

  • 在 Google Search Console 中验证的网域。此网域不需要与公开发布的网站相关联,我们只需引用您的网域即可。

  • Google Play 管理中心内具有 com.android.vending.BILLING 权限的 Android 应用。在 Google Play 管理中心内,您的数字商品将是与此应用关联的“应用内购商品”。

    您还需要在 Play 管理中心内使用此应用创建一个版本,但如果您不希望发布该版本,则可以创建封闭式 Alpha 版

    如果您还没有 Android 应用,请按照关联 Android 应用说明进行操作。

  • Google Play 管理中心中的一个或多个受管理商品,即您通过 Action 销售的数字商品。请注意,在满足 Android 应用的前提条件之前,您无法在 Play 管理中心内创建受管理的商品。

    如果您还没有受管理的商品,请按照创建数字商品说明操作。

关联 Android 应用

如果您当前在 Google Play 管理中心内没有任何具有结算权限的 Android 应用,请按以下步骤操作:

  1. Android Studio 或您选择的 Android IDE 中,创建一个新项目。在项目设置提示中选择一些选项,以创建非常基本的应用。
  2. 为项目指定软件包名称,例如 com.mycompany.myapp。 请勿将此名称保留为默认值,因为您无法将包含 com.example 的软件包上传到 Play 管理中心。
  3. 打开应用的 AndroidManifest.xml 文件。
  4. manifest 元素中添加以下代码行:

    <uses-permission android:name="com.android.vending.BILLING" />

    您的 AndroidManifest.xml 文件应如以下代码块所示:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.mycompany.myapp">
        <uses-permission android:name="com.android.vending.BILLING" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme" />
    </manifest>
    
  5. 将您的应用构建为已签名的 APK。在 Android Studio 中,请按以下步骤操作:

    1. 依次进入 BuildGenerate Signed Bundle / APK
    2. 点击下一步
    3. Key store path 下,点击 Create new
    4. 填写每个字段,然后点击 OK。记下您的密钥库密码密钥密码,并将它们存储在安全的地方,因为稍后您将用到它们。
    5. 点击下一步
    6. 选择发布
    7. 选择 V1(JAR 签名)
    8. 点击完成
    9. 几秒钟后,Android Studio 会生成 app-release.apk 文件。找到此文件以供日后使用。
  6. Google Play 管理中心内,创建一个新应用。

  7. 前往应用版本

  8. 封闭式轨道下,依次转到管理Alpha 版

  9. 点击创建版本按钮。

  10. 让 Google 管理和保护您的签名密钥下,输入您的签名密钥信息。

  11. 上传您的 APK 文件。

  12. 点击保存

创建数字商品

如果您当前在 Play 管理中心内没有任何数字商品,请按以下步骤操作:

  1. Google Play 管理中心中,依次前往应用内商品受管理的商品。如果您看到警告,请按照上述说明创建 Android 应用,或点击链接创建商家资料。
  2. 点击创建受管理的商品
  3. 填写数字商品的相关字段。记下商品 ID,以便在 Action 中引用此商品。
  4. 点击保存
  5. 针对要销售的每种产品重复执行步骤 2-4。

Google Play 管理中心内的消耗品示例。

准备 Actions 项目

在 Google Play 管理中心内设置数字商品后,您必须启用数字交易,并将您的 Actions 项目与 Play 应用相关联。

初始设置

如需在您的 Actions 项目中开启数字商品交易,请按以下步骤操作:

  1. Actions 控制台中,打开您的项目或创建新项目。
  2. 转到部署,然后选择 Directory information
  3. 其他信息交易下,勾选您的操作是否使用 Digital Purchase API 执行数字商品交易下的复选框。
  4. 点击保存

创建数字商品 API 密钥

如需向数字商品 API 发送请求,您需要下载与您的 Actions 控制台项目关联的 JSON 服务帐号密钥。

如需检索您的服务帐号密钥,请按以下步骤操作:

  1. Actions 控制台中,点击右上角的三点状图标,然后点击项目设置
  2. 查找 Action 的项目 ID
  3. 访问以下链接,将“<project_id>”替换为您的项目 ID:https://console.developers.google.com/apis/credentials?project=project_id
  4. 在主导航栏中,前往凭据
  5. 在随即显示的页面上,点击创建凭据,然后点击服务帐号密钥
  6. 转到 Service Account(服务帐号),然后点击 New Service Account(新建服务帐号)。
  7. 为服务账号命名,例如 digitaltransactions。
  8. 点击创建
  9. 角色设置为项目 > Owner
  10. 点击继续
  11. 点击创建密钥
  12. 选择 JSON 密钥类型。
  13. 点击创建密钥,然后下载 JSON 服务帐号密钥。

请将此服务账号密钥保存在安全的位置。您将在执行方式中使用此密钥为 Digital Purchases API 创建客户端。

关联到您的 Play 广告资源

如需从 Actions 项目访问您的数字商品,请将您的网域和应用作为关联属性关联。

如需将您的 Play 管理中心网域和应用关联到您的 Actions 项目,请按以下步骤操作:

  1. Actions 控制台中,依次前往部署品牌验证
  2. 如果您尚未关联任何媒体资源,请先关联一个网站:

    1. 点击网络媒体资源 (</>) 按钮。
    2. 输入您的域名的网址,然后点击关联

    Google 会向已在 Google Search Console 中针对该网域完成验证的个人发送一封电子邮件,其中包含进一步的说明。一旦此电子邮件的收件人按照上述步骤完成操作,该网站应该就会显示在品牌验证下。

  3. 有了至少一个关联网站后,请按照以下步骤关联您的 Android 应用:

    1. Actions 控制台中,依次前往部署品牌验证
    2. 点击关联应用
    3. 在显示的页面上,按照说明在 Play 管理中心验证您的网域。选择包含您的数字商品的 Play 应用,然后输入品牌验证页面上显示的确切网域网址。

      Google 会再次向经过验证的网域所有者发送验证电子邮件。在他们批准验证后,您的 Play 应用应显示在品牌验证下。

    4. 启用访问 Play 购买交易

此图片显示了与 Actions 项目相关联的网站和应用。

构建购买流程

准备好 Action 项目和数字商品库存后,在对话执行 webhook 中构建数字商品购买流程。

1. 设置 DigitalPurchases API 客户端

在对话执行方式 webhook 中,使用您的服务帐号 JSON 密钥和 https://www.googleapis.com/auth/actions.purchases.digital 范围创建 JWT 客户端。

以下 Node.js 代码为 Digital purchase API 创建了一个 JWT 客户端:

  const serviceAccount = {'my-file.json'};
  const request = require('request');
  const {google} = require('googleapis');

  const jwtClient = new google.auth.JWT(
    serviceAccount.client_email, null, serviceAccount.private_key,
    ['https://www.googleapis.com/auth/actions.purchases.digital'],
    null
  );

2. 收集信息

在用户能够进行购买之前,您的 Action 会收集有关用户能否购物以及您的商品目录中有哪些商品的信息。

2. a. 验证数字购买要求

最好先将用户的帐号设置为可执行交易,然后再为其提供购买选项。您应该过渡到 DigitalPurchaseCheck 场景,该场景会检查用户是否已通过验证,用户是否在允许的 surface(智能显示屏、智能音箱或 Android)上执行交易,以及用户位于支持数字交易的语言区域。

如需创建数字购买检查场景,请按以下步骤操作:

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

数字购买检查会产生以下结果之一:

  • 如果满足这些要求,系统就会为会话参数设置成功条件,然后您可以继续允许用户购买数字商品。
  • 如果无法满足一项或多项要求,则会话参数会设为失败条件。在这种情况下,您应该将对话从事务体验转向其他方式,或结束对话。

如需处理“数字购买”检查结果,请按以下步骤操作:

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

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

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

    candidates:
      - first_simple:
          variants:
            - speech: >-
                You are ready to purchase digital goods.
    
  6. Transition 下,选择其他场景,允许用户继续对话并继续进行交易。

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

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

    candidates:
      - first_simple:
          variants:
            - speech: Sorry you cannot perform a digital purchase.
    
  9. Transition 下,选择 End sessions 以结束对话。

2. b. 收集可用的广告资源

使用 DigitalPurchases API 请求您当前在 Play 商店中上架的商品目录,然后将该商品目录内置到每件商品的 JSON 对象数组中。您稍后会引用此数组来向用户显示哪些选项可供购买。

您的每种数字商品都以 JSON 格式表示为一个 SKU。以下 Node.js 代码概述了每个 SKU 的预期格式:

body = {
  skus: [
    skuId: {
      skuType: one of "SKU_TYPE_IN_APP" or "SKU_TYPE_SUBSCRIPTION"
      id: string,
      packageName: string
    }
    formattedPrice: string,
    title: string,
    description: string
  ]
}

https://actions.googleapis.com/v3/packages/{packageName}/skus:batchGet 端点发送 POST 请求,其中 {packageName} 是应用在 Google Play 管理中心内的软件包名称(例如 com.myapp.digitalgoods),并将结果的格式设置为 SKU 对象数组。

如需仅检索结果数组中的特定数字商品,请列出您希望在 body.ids 中上架的数字商品的商品 ID(如 Google Play 管理中心内每个应用内商品下所示)。

以下 Node.js 代码从 Digital Purchases API 请求可用商品列表,并将结果的格式设置为 SKU 数组:

return jwtClient.authorize((err, tokens) => {
    if (err) {
      throw new Error(`Auth error: ${err}`);
    }

    const packageName = 'com.example.projectname';

    request.post(`https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`, {
      'auth': {
        'bearer': tokens.access_token,
      },
      'json': true,
      'body': {
        'conversationId': conv.session.id,
        'skuType': 'SKU_TYPE_IN_APP',
        // This request is filtered to only retrieve SKUs for the following product IDs
        'ids': ['consumable.1']
      },
    }, (err, httpResponse, body) => {
      if (err) {
        throw new Error(`API request error: ${err}`);
      }
      console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
      console.log(JSON.stringify(body));
    });
  });
});

3. 构建订单

如需启动用户进行数字购买交易,请呈现可供购买的数字商品列表。您可以使用各种丰富的响应类型来表示您的股票并提示用户进行选择。

以下 Node.js 代码会读取 SKU 对象的目录数组,并创建一个列表响应(每个响应都有一个列表项):

const items = [];
const entries = [];
skus.forEach((sku) => {
   const key = `${sku.skuId.skuType},${sku.skuId.id}`
   items.push({
       key: key
   });
   entries.push({
       name: key,
       synonyms: [],
       display: {
           title: sku.title,
           description: `${sku.description} | ${sku.formattedPrice}`,
       }
   });
});

conv.session.typeOverrides = [{
   name: 'type_name',
   mode: 'TYPE_REPLACE',
   synonym: {
       entries: entries
   }
}];

conv.add(new List({
   title: 'List title',
   subtitle: 'List subtitle',
   items: items,
}));

基于用户选择创建购买交易

用户选择商品后,您就可以创建订单了。为此,您可以在与所选商品关联的槽位上调用网络钩子来创建订单。在履单中,将订单数据保存到会话参数。订单对象用于同一会话的多种场景中。

conv.session.params.purchase = {
  "@type": "type.googleapis.com/google.actions.transactions.v3.CompletePurchaseValueSpec",
  "skuId": {
    "skuType": "<SKU_TYPE_IN_APP>",
    "id": "<SKU_ID>",
    "packageName": "<PACKAGE_NAME>"
  },
  "developerPayload": ""
};

在 Actions Builder 中,您可以改用 JSON 编辑器,通过上述订单对象配置槽。这两种实现对 CompletePurchaseValueSpec 使用相同的格式,详见 JSON 网络钩子载荷参考文档

4. 完成购买

用户选择商品后,您就可以完成购买了。填充与所选项关联的槽后,您应该过渡到执行完整购买的场景。

创建完整的购买场景

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

5. 处理结果

类型为 actions.type.CompletePurchaseValue 的槽可能会产生以下结果:

  • PURCHASE_STATUS_OK:购买成功。此时事务已完成,因此请退出事务流程并返回到您的对话。
  • PURCHASE_STATUS_ALREADY_OWNED:由于用户已经拥有该商品,因此交易失败。您可以检查用户之前购买的内容并定制所显示的商品,使用户无法重新购买已经拥有的商品,从而避免此错误。
  • PURCHASE_STATUS_ITEM_UNAVAILABLE:由于所请求的商品不可用,因此交易失败。请在临近购买时检查可用 SKU,以避免此错误。
  • PURCHASE_STATUS_ITEM_CHANGE_REQUESTED:交易失败,因为用户决定购买其他商品。重新提示构建订单,以便用户立即做出其他决定。
  • PURCHASE_STATUS_USER_CANCELLED:由于用户取消了购买流程,因此交易失败。由于用户提前退出了流程,因此请询问用户是重试事务还是完全退出事务。
  • PURCHASE_STATUS_ERROR:交易因未知原因而失败。请告知用户交易失败,并询问他们是否要重试。
  • PURCHASE_STATUS_UNSPECIFIED:交易因未知原因而失败,导致状态未知。处理此错误状态时,请告知用户交易失败,并询问他们是否要重试。

您应从 CompletePurchase 场景中处理其中的每个结果。

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

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

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

    candidates:
      - first_simple:
          variants:
            - speech: >-
                Your purchase was successful.
    
  6. Transition 下,选择 End sessions 以结束对话。

针对您希望支持的每种购买结果重复上述步骤。

6. 确保购买交易可重复

交易成功后,向 digital purchase API 发送 POST 请求即可消耗该商品,允许用户再次购买。使用在 session.id 中找到的会话 ID 将您的请求发送到 https://actions.googleapis.com/v3/conversations/{sessionId}/entitlement:consume 端点。

您的 POST 请求还必须包含与用户购买交易关联的购买令牌对象,该对象可在用户请求 JSON 中的 packageEntitlements.entitlements.inAppDetails.inAppPurchaseData.purchaseToken 下找到。

以下代码会向 Digital Purchases API 发送 consume 请求,并报告该请求是否成功:

request.post(`https://actions.googleapis.com/v3/conversations/${conv.session.id}/entitlement:consume`, {
  'auth': {
    'bearer': tokens.access_token,
  },
  'json': true,
  'body': {
  // This purchase token is in both the purchase event and the user's entitlements
  // in their request JSON
    "purchaseToken": entitlement.purchaseToken
  },
  }, (err, httpResponse, body) => {
    if (err) {
     throw new Error(`API request error: ${err}`);
    }
  console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
  console.log(JSON.stringify(httpResponse));
  console.log(JSON.stringify(body));
  resolve(body);
});

// Make sure the consume request was successful. In production, don't notify the user; handle failures on the back end
return consumePromise.then(body => {
  const consumed = Object.keys(body).length === 0;
  if (consumed) {
    conv.add(`You successfully consumed ${id}`);
  } else {
    conv.add(`Failed to consume: ${id}`);
  }
});

反映用户的购买行为

当用户查询您的 Action 时,请求 JSON 的 user 对象会包含用户的购买交易列表。检查此信息,并根据用户付费购买的内容更改 Action 的响应。

以下示例代码展示了一个请求的 user 对象,其中包含之前针对 com.digitalgoods.application 软件包进行的应用内购买的 packageEntitlements

{
  "handler": {
    "name": "handler_name"
  },
  "intent": {
    "name": "actions.intent.MAIN",
    "params": {},
    "query": ""
  },
  "scene": {
    "name": "SceneName",
    "slotFillingStatus": "UNSPECIFIED",
    "slots": {}
  },
  "session": {
    "id": "example_session_id",
    "params": {},
    "typeOverrides": []
  },
  "user": {
    "locale": "en-US",
    "params": {
      "verificationStatus": "VERIFIED"
      "packageEntitlements": [
        {
          "packageName": "com.digitalgoods.application",
          "entitlements": [
            {
              "sku": "non-consumable.1",
              "skuType": "SKU_TYPE_IN_APP"
            }
            {
              "sku": "consumable.2",
              "skuType": "SKU_TYPE_IN_APP"
            }
          ]
        },
        {
          "packageName": "com.digitalgoods.application",
          "entitlements": [
            {
              "sku": "annual.subscription",
              "skuType": "SKU_TYPE_SUBSCRIPTION",
              "inAppDetails": {
                "inAppPurchaseData": {
                  "autoRenewing": true,
                  "purchaseState": 0,
                  "productId": "annual.subscription",
                  "purchaseToken": "12345",
                  "developerPayload": "HSUSER_IW82",
                  "packageName": "com.digitalgoods.application",
                  "orderId": "GPA.233.2.32.3300783",
                  "purchaseTime": 1517385876421
                },
                "inAppDataSignature": "V+Q=="
              }
            }
          ]
        }
      ]
     }
   },
  "homeStructure": {
    "params": {}
  },
  "device": {
    "capabilities": [
      "SPEECH",
      "RICH_RESPONSE",
      "LONG_FORM_AUDIO"
    ]
  }
}

测试您的项目

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

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