构建消耗型数字交易 (Dialogflow)

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

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

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

限制和审核指南

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

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

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

交易流程

本指南将简要介绍数字商品交易流程中发生的每个开发步骤。您的 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 中之前,您需要满足以下前提条件:

  • 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. 验证交易要求

最好先将用户的帐号设置为执行交易,然后再为其提供购买选项。此步骤包括检查用户是否已配置付款方式,以及用户所处的语言区域支持数字交易。在事务流程开始时,使用 DIGITAL_PURCHASE_CHECK 帮助程序通过 Google 助理验证用户的事务配置。

以下 Node.js 代码在对话开始时使用 DIGITAL_PURCHASE_CHECK

app.intent('Default Welcome Intent', async (conv, { SKU }) => {
  // Immediately invoke digital purchase check intent to confirm
  // purchase eligibility.
  conv.ask(new DigitalPurchaseCheck());
});

在对话参数中找到 DIGITAL_PURCHASE_CHECK_RESULT 形式的此检查结果。根据此结果,您要么继续交易流程,要么离开,提示他们检查 Google Pay 配置。

以下 Node.js 代码会处理要求检查结果:

app.intent('Digital Purchase Check', async (conv) => {
  const arg = conv.arguments.get('DIGITAL_PURCHASE_CHECK_RESULT');
  if (!arg || !arg.resultType) {
    conv.close('Digital Purchase check failed. Please check logs.');
    return;
  }
  // User does not meet necessary conditions for completing a digital purchase
  if (arg.resultType === 'CANNOT_PURCHASE' || arg.resultType === 'RESULT_TYPE_UNSPECIFIED') {
    conv.close(`It looks like you aren't able to make digital purchases. Please check your Google Pay configuration and try again.`);
    return;
  }
  conv.ask('Welcome to the Digital Goods Sample. Would you like to see what I have for sale?');
});

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

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

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

body = {
  skus: [
    skuId: {
      skuType: one of "APP" or "UNSPECIFIED"
      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': conversationId,
        'skuType': '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 对象的目录数组,并创建一个列表响应(每个响应都有一个列表项):

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

4. 完成购买

如需完成购买,请结合使用 COMPLETE_PURCHASE 帮助程序 intent 与用户所选的商品。

以下 Node.js 代码会处理用户从列表响应中选择的 SKU 并请求包含该信息的 COMPLETE_PURCHASE intent:

app.intent('Send Purchase', (conv, params, option) => {
  let [skuType, id] = option.split(',');

  conv.ask(new CompletePurchase({
    skuId: {
      skuType: skuType,
      id: id,
      packageName: <PACKAGE_NAME>,
    },
  }));
});

5. 处理结果

购买完成后,它会触发 actions_intent_COMPLETE_PURCHASE Dialogflow 事件(或 actions.intent.COMPLETE_PURCHASE Actions SDK intent),并提供描述结果的 COMPLETE_PURCHASE_VALUE 参数。构建一个由此事件触发的 intent,并将结果传达给用户。

处理以下可能的购买结果:

  • 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:交易因未知原因而失败,导致状态未知。处理此错误状态时,请告知用户交易失败,并询问他们是否要重试。

以下 Node.js 代码会读取 COMPLETE_PURCHASE_VALUE 参数并处理每个结果:

app.intent('Purchase Result', (conv) => {
  const arg = conv.arguments.get('COMPLETE_PURCHASE_VALUE');
  console.log('User Decision: ' + JSON.stringify(arg));
  if (!arg || !arg.purchaseStatus) {
    conv.close('Purchase failed. Please check logs.');
    return;
  }
  if (arg.purchaseStatus === 'PURCHASE_STATUS_OK') {
    conv.close(`Purchase completed! You're all set!`);
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ALREADY_OWNED') {
    conv.close('Purchase failed. You already own this item.');
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ITEM_UNAVAILABLE') {
    conv.close('Purchase failed. Item is not available.');
  } else if (arg.purchaseStatus === 'PURCHASE_STATUS_ITEM_CHANGE_REQUESTED') {
    // Reprompt with your item selection dialog
  }  else {
    conv.close('Purchase Failed:' + arg.purchaseStatus);
  }
});

6. 确保购买交易可重复

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

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

body = {
  skus: [
    skuId: {
      skuType: one of "APP" or "UNSPECIFIED"
      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': conversationId,
        'skuType': '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));
    });
  });
});

反映用户的购买行为

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

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

  "user": {
    "userId": "xxxx",
    "locale": "en-US",
    "lastSeen": "2018-02-09T01:49:23Z",
    "packageEntitlements": [
      {
        "packageName": "com.digitalgoods.application",
        "entitlements": [
          {
            "sku": "non-consumable.1",
            "skuType": "APP"
          }
          {
            "sku": "consumable.2",
            "skuType": "APP"
          }
        ]
      },
      {
        "packageName": "com.digitalgoods.application",
        "entitlements": [
          {
            "sku": "annual.subscription",
            "skuType": "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=="
            }
          }
        ]
      }
    ]
  },
  "conversation": {
    "conversationId": "1518141160297",
    "type": "NEW"
  },
  "inputs": [
    {
      "intent": "actions.intent.MAIN",
      "rawInputs": [
        {
          "inputType": "VOICE",
          "query": "Talk to My Test App"
        }
      ]
    }
  ],
  ...
}