PAYATHON 2026

Android subscription 调用 Google API 时的 OAuth2 认证问题

支付小周

结论

用 JWT 换取 access token,只完成了 Google OAuth 2.0 身份认证。service account 不会因此自动获得 Google Play 开发者账号或应用的访问权限。

服务器要读取订阅信息,还需要完成以下配置:

  1. 在 Google Cloud 项目中启用 Google Play Android Developer API。
  2. 创建 service account,并在服务器端使用它的凭据获取 access token。
  3. 在 Google Play Console 中将该 service account 的邮箱添加为用户,并授予目标应用及订阅查询所需的权限。
  4. 请求中的 packageName 必须属于已授权的 Play Console 开发者账号,并与实际上架应用的 application ID 完全一致。

出现 "developer account does not own the application" 时,问题通常不在 JWT 签名,而在于 Play Console 没有授权、授权了错误的开发者账号或应用,或者请求中的 packageName 不正确。

为什么拿到 access token 后仍然无法访问

service account 涉及两套彼此独立的权限:

  • Google Cloud IAM 决定它能否以 Google 身份完成认证。
  • Google Play Console 决定这个身份可以访问哪些开发者账号、应用和业务数据。

下面的 scope:

https://www.googleapis.com/auth/androidpublisher

只说明这个 token 用于访问 Android Publisher API,并不代表该身份已经获得任何 Play Console 应用的权限。

即使 access token 有效,只要 service account 没有被添加到正确的 Play Console 账号,API 仍可能返回权限或应用所有权相关错误。

正确配置步骤

1. 选择或创建 Google Cloud 项目

在 Google Cloud Console 中选择一个用于服务端调用的项目,并启用:

Google Play Android Developer API

如果 Play Console 的相关页面提供 Cloud 项目关联入口,请确认关联的是创建 service account 并启用 API 的那个项目。不同版本的 Play Console 页面名称可能不同,但不要在项目 A 中创建 service account,却只在项目 B 中启用 API。

2. 创建 service account

在该 Cloud 项目中创建 service account,例如:

play-subscription-reader@your-project.iam.gserviceaccount.com

服务器可以通过以下任一方式取得凭据:

  • 在 Google Cloud、GKE、Compute Engine 或支持 Workload Identity 的环境中使用默认凭据。
  • 无法使用工作负载身份时,使用 service account JSON key。

JSON key 是长期密钥,应保存在服务端的密钥管理系统中。不要将它打包进 Android 应用,也不要提交到代码仓库。

3. 在 Google Play Console 中授权 service account

进入拥有该 Android 应用的 Google Play Console 开发者账号,在用户和权限管理页面添加 service account 邮箱:

play-subscription-reader@your-project.iam.gserviceaccount.com

邮箱必须完全匹配。仅在 Google Cloud IAM 中为它添加角色是不够的。

授权范围至少应包括:

  • 目标应用的访问权限,或者相应的账号级访问权限。
  • 查看订阅及订单所需数据的权限。
  • 如果服务还需要退款、撤销或取消订阅,还要授予管理订单和订阅的权限。

Play Console 的权限名称会随界面调整。某些旧版界面使用类似”查看财务数据”的权限控制订阅查询,新版界面可能将查看订单、管理订单和订阅拆分为不同权限。请遵循最小权限原则,并确认授权范围覆盖当前请求所针对的应用。

权限变更有时需要一段时间才能生效。完成授权后可以稍等再试,但等待不能替代正确的权限配置。

4. 核对请求参数

至少检查以下参数:

packageName
subscriptionId
purchaseToken

其中:

  • packageName 是 Play Console 中目标应用的 application ID,例如 com.example.app。
  • subscriptionId 是订阅商品 ID,不是订单号。
  • purchaseToken 来自 Android 端成功购买后的结果,应原样、安全地发送到服务器。
  • 不要将测试包名、旧包名、渠道包名或服务端项目 ID 用作 packageName。
  • purchase token 必须与对应应用及订阅购买记录匹配。

PHP 服务端示例

建议通过 Composer 安装当前版本的 Google API PHP Client,无需手动生成 JWT:

composer require google/apiclient

使用 service account JSON 凭据:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Google\Client;
use Google\Service\AndroidPublisher;

$client = new Client();
$client->setAuthConfig('/secure/path/service-account.json');
$client->setScopes([
    AndroidPublisher::ANDROIDPUBLISHER,
]);

$publisher = new AndroidPublisher($client);

$packageName = 'com.example.app';
$subscriptionId = 'premium_monthly';
$purchaseToken = 'purchase-token-received-from-the-app';

try {
    $purchase = $publisher->purchases_subscriptions->get(
        $packageName,
        $subscriptionId,
        $purchaseToken
    );

    echo json_encode($purchase, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (\Google\Service\Exception $e) {
    error_log($e->getMessage());
    throw $e;
}

客户端库会负责:

  1. 使用 service account 私钥签署 JWT assertion。
  2. 向 Google OAuth 2.0 token endpoint 换取 access token。
  3. 缓存 token,并在需要时刷新。
  4. 将 bearer token 添加到 Android Publisher API 请求中。

不需要直接调用 generateAssertion(),也不要把生成的 JWT assertion 当成最终的 access token。

使用 Application Default Credentials

如果服务器运行在支持 Google 身份绑定的环境中,优先使用 Application Default Credentials,这样可以避免保存 JSON 私钥:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Google\Client;
use Google\Service\AndroidPublisher;

$client = new Client();
$client->useApplicationDefaultCredentials();
$client->setScopes([
    AndroidPublisher::ANDROIDPUBLISHER,
]);

$publisher = new AndroidPublisher($client);

即使使用这种方式,仍须将运行时使用的 service account 邮箱添加到 Google Play Console。Cloud 环境自动提供凭据,不代表它自动获得了 Play Console 权限。

关于新的订阅查询接口

较新的订阅集成可以使用 purchases.subscriptionsv2.get,通常只需要:

packageName
token

对应的 REST 请求为:

GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{token}
Authorization: Bearer ACCESS_TOKEN

PHP 客户端中的具体方法名取决于已安装的 google/apiclient-services 版本,应以该版本生成的 AndroidPublisher 类为准。旧版 purchases.subscriptions.get 示例仍可用于说明认证与权限之间的关系,但新接入时应先查看当前 Android Publisher API 文档推荐的订阅接口。

无论使用旧接口还是 subscriptionsv2,更换 endpoint 都不会免除 service account 的 Play Console 授权要求。

排查顺序

遇到应用所有权或权限错误时,可以按以下顺序检查:

  1. 解码 access token 的相关信息,确认实际认证身份是预期的 service account。
  2. 确认 token 包含 androidpublisher scope。
  3. 确认 Android Publisher API 已在正确的 Cloud 项目中启用。
  4. 确认 service account 邮箱已添加到拥有该应用的 Play Console 开发者账号。
  5. 确认它拥有目标应用的访问权限,而不是只有账号内其他应用的权限。
  6. 确认已授予订阅或订单数据权限。
  7. 确认 packageName 与 Play Console 中的 application ID 完全一致。
  8. 确认 purchaseToken 来自该包名下的真实购买,或有效的许可测试购买。
  9. 如果刚修改过权限,等待权限生效,然后重新获取 access token 并重试。

这里不需要 end-user consent,也不需要浏览器授权流程。服务端使用的是 OAuth 2.0 service account 流程。用户在购买时完成 Google Play Billing 交易,服务器再通过已获得 Play Console 授权的 service account 查询交易状态。

备注:内容仅供参考。