You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Billing Library 6中准确获取有效订阅列表并解决状态不一致

解决Google Play订阅状态不一致与服务器端校验方案

一、客户端问题排查与优化

你遇到的跨设备状态不一致,核心原因是queryPurchasesAsync默认返回本地缓存的购买记录,而非实时从Google Play服务器拉取,导致不同设备缓存不同步。另外,仅靠isAutoRenewing()和PurchaseState判断状态也不够严谨,需要补充更多维度校验。

客户端代码优化

  1. 强制从服务器拉取最新数据:在QueryPurchasesParams中添加setForceRefresh(true),跳过本地缓存
  2. 综合判断订阅有效性:结合过期时间、购买状态、自动续费标识,覆盖「取消自动续费但仍在订阅期」「订阅过期但缓存未更新」等场景

优化后的代码示例:

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .setForceRefresh(true) // 强制从服务器拉取最新数据
        .build(),
    new PurchasesResponseListener() {
        public void onQueryPurchasesResponse(
            @NonNull BillingResult billingResult,
            @NonNull List<Purchase> purchases) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
                long currentTime = System.currentTimeMillis();
                for (Purchase purchase : purchases) {
                    boolean isSubscriptionActive = false;
                    // 1. 购买状态为已购买,且未过期
                    boolean isPurchasedAndNotExpired = purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
                            && purchase.getExpirationTime() > currentTime;
                    // 2. 处理自动续费状态:即使取消自动续费,只要在订阅期内仍有效
                    boolean isAutoRenewing = purchase.isAutoRenewing();

                    if (isPurchasedAndNotExpired) {
                        isSubscriptionActive = true;
                        // 可区分「自动续费中」和「已取消自动续费但仍在有效期」
                        if (isAutoRenewing) {
                            // 活跃订阅,自动续费开启
                        } else {
                            // 订阅仍有效,但已取消自动续费,到期后将终止
                        }
                    } else {
                        // 订阅已过期或未完成购买
                    }
                }
            } else {
                // 处理查询失败逻辑(比如网络问题)
            }
        }
    }
);

二、为什么必须做服务器端校验?

客户端校验存在天然缺陷:

  • 本地缓存延迟或篡改风险
  • 跨设备状态无法实时同步
  • 部分订阅状态变更(比如Google Play后台退款、账号封禁)无法通过客户端API感知

服务器端校验是获取订阅真实状态的唯一可靠方式,直接对接Google Play Developer API获取官方数据,不受客户端环境影响。

三、Node.js服务器端实现步骤

1. 前置准备

  • 在Google Cloud Console创建项目,启用「Google Play Developer API」
  • 创建服务账号,下载JSON格式的密钥文件
  • 在Google Play Console的「用户权限」页面,给该服务账号授予「查看应用权限」(仅查询订阅状态足够)

2. 项目初始化与依赖安装

创建Node.js项目,安装必要依赖:

npm init -y
npm install googleapis express

3. 核心代码实现

以下是基础的订阅状态查询服务示例:

const { google } = require('googleapis');
const express = require('express');
const app = express();
app.use(express.json());

// 初始化Google Play Developer API客户端
const auth = new google.auth.GoogleAuth({
  keyFile: './service-account-key.json', // 替换为你的服务账号密钥文件路径
  scopes: ['https://www.googleapis.com/auth/androidpublisher']
});

const androidpublisher = google.androidpublisher({
  version: 'v3',
  auth: auth
});

// 查询单个订阅状态接口
app.post('/api/subscription/status', async (req, res) => {
  const { packageName, subscriptionId, purchaseToken } = req.body;
  if (!packageName || !subscriptionId || !purchaseToken) {
    return res.status(400).json({ error: '缺少必要参数' });
  }

  try {
    const response = await androidpublisher.purchases.subscriptions.get({
      packageName: packageName,
      subscriptionId: subscriptionId,
      token: purchaseToken
    });

    const subscription = response.data;
    // 解析关键状态字段
    const status = {
      isActive: subscription.expiryTimeMillis > Date.now(),
      autoRenewing: subscription.autoRenewing,
      expiryTime: subscription.expiryTimeMillis,
      cancelReason: subscription.cancelReason, // 0=用户取消,1=账单问题,2=服务取消
      cancelSurveyResult: subscription.cancelSurveyResult // 用户取消时的反馈(可选)
    };

    res.json(status);
  } catch (error) {
    console.error('查询订阅状态失败:', error);
    res.status(500).json({ error: '查询失败', details: error.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

4. 客户端与服务器同步策略

  • 实时同步:客户端在启动、订阅完成、订阅变更(比如用户取消自动续费)时,调用服务器接口获取最新状态
  • 定期同步:服务器每天定时拉取所有用户的订阅状态(通过purchases.subscriptions.list接口),更新本地数据库,减少实时查询的API调用量
  • 本地缓存:客户端保存服务器返回的状态,设置合理的过期时间(比如1小时),避免频繁请求服务器

四、最佳实践总结

  1. 客户端始终强制拉取最新数据,但仅作为临时状态展示,最终以服务器校验结果为准
  2. 服务器端校验必须覆盖所有订阅状态判断逻辑,客户端不做核心权限控制(比如解锁付费功能必须依赖服务器返回的有效状态)
  3. 定期清理服务器中过期的订阅记录,避免无效数据堆积

内容的提问来源于stack exchange,提问作者Sujith S Manjavana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 01:17:41