PAYATHON 2026

Google Play Billing 如何判断待确认的价格变更

技术老齐

结论

Google Play Billing Library 没有提供与 Google Play Developer API 中 subscription.priceChange、priceChange.state 完全对应的客户端查询接口。

Billing Library 可以发起价格变更确认流程,但无法可靠查询某个订阅是否存在待确认的价格变更,Purchase 等客户端对象也不会返回 priceChange.state。这项状态应由服务端调用 Google Play Developer API 判断,再将”是否需要提示用户”这一业务结果返回给应用。

订阅价格变更相关接口已经历多次调整。如果项目使用较新的 Billing Library 或新版订阅模型,应以当前官方文档为准。旧版价格确认流程 API 可能已经弃用,或不再适用于新创建的价格变更。

为什么客户端查不到

Billing Library 主要负责:

  • 查询商品和订阅方案
  • 启动购买流程
  • 获取当前设备账号下的购买记录
  • 确认购买或消费商品
  • 接收购买流程结果

订阅价格变更是否仍待用户确认,则是由 Google Play 后台维护的订阅状态。客户端返回的 Purchase 对象通常只有购买令牌、商品 ID、购买状态和确认状态等信息,不包含 REST API 中的 priceChange 对象。

即使客户端查到了最新价格,也不能据此判断用户是否需要确认。价格发生变化、价格已经生效、用户仍有操作待确认,分别是不同的状态。

推荐的处理方式

可以采用下面的流程:

  1. 应用通过 Billing Library 获取订阅的 purchaseToken。
  2. 应用将 purchaseToken 和订阅商品 ID 发送给自己的服务端。
  3. 服务端使用 Google Play Developer API 查询订阅状态。
  4. 服务端检查响应中的价格变更信息。
  5. 服务端只向应用返回业务所需的结果,例如 pendingPriceChange: true。
  6. 应用根据结果展示说明。如果当前使用的 Billing Library 版本仍支持价格变更确认流程,也可以启动该流程。

不要在 Android 应用中直接调用 Google Play Developer API。该 API 通常需要服务账号凭据,将凭据打包进 APK 会带来严重的密钥泄露风险。

服务端判断示例

下面是基于题目所述旧版订阅资源结构的示意代码。客户端方法名和返回结构取决于服务端使用的 Google API SDK 版本。

async function getPriceChangeStatus(androidPublisher, packageName, subscriptionId, purchaseToken) {
  const response = await androidPublisher.purchases.subscriptions.get({
    packageName,
    subscriptionId,
    token: purchaseToken
  });

  const subscription = response.data;
  const priceChange = subscription.priceChange;

  if (!priceChange) {
    return {
      hasPriceChange: false,
      pendingPriceChange: false
    };
  }

  return {
    hasPriceChange: true,
    pendingPriceChange: isOutstandingPriceChange(priceChange.state)
  };
}

function isOutstandingPriceChange(state) {
  // 应根据当前 Google Play Developer API 文档定义映射状态,
  // 不要仅凭示例代码猜测枚举值。
  return state === YOUR_OUTSTANDING_STATE_VALUE;
}

不能因为 priceChange 字段存在,就直接启动确认流程。该字段只能说明订阅关联了价格变更,还要结合 priceChange.state 判断用户是否尚未完成确认。

新版订阅查询接口的响应可能不再采用上述字段结构。这种情况下,需要按照当前接口返回的订阅项目、价格变更详情和状态字段重新实现判断,不能直接套用旧版模型。

Android 端示例

应用可以把购买令牌交给服务端,再由服务端返回简化后的状态:

data class PriceChangeStatus(
    val hasPriceChange: Boolean,
    val pendingPriceChange: Boolean
)

fun checkSubscriptionPriceChange(purchase: Purchase) {
    val purchaseToken = purchase.purchaseToken

    subscriptionApi.getPriceChangeStatus(
        productId = purchase.products.first(),
        purchaseToken = purchaseToken
    ).enqueue { result ->
        if (result.pendingPriceChange) {
            showPriceChangeExplanation()
        }
    }
}

如果当前使用的 Billing Library 版本仍提供 launchPriceChangeConfirmationFlow,可以先向用户说明变更内容,再启动旧版确认流程:

val params = PriceChangeFlowParams.newBuilder()
    .setSkuDetails(skuDetails)
    .build()

billingClient.launchPriceChangeConfirmationFlow(
    activity,
    params
) { billingResult ->
    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
        // 这里只表示确认流程返回成功。
        // 最终订阅状态仍应由服务端重新查询并校验。
    }
}

这段代码只适用于仍包含 PriceChangeFlowParams 和 launchPriceChangeConfirmationFlow 的 Billing Library 版本。如果当前依赖中没有这些类型,不要为了使用示例而盲目降级 Billing Library,而应遵循当前 Google Play 的订阅价格变更流程。

注意事项

不要把流程回调当作最终状态

确认页面返回 OK 后,服务端状态可能不会立即更新。应用应稍后重新查询服务端,不要直接在本地将订阅标记为”已接受价格变更”。

避免每次启动都弹出确认页面

应用可以在启动时查询状态,但不应无条件启动确认流程。只有服务端确认状态仍待处理时,才向用户展示入口。同时还要处理用户取消、网络失败和状态同步延迟。

服务端状态才是判断依据

客户端缓存可以用于优化界面,但不能作为订阅权益或价格变更状态的最终依据。订阅续期、取消、暂停和价格变化都有可能在应用未运行时发生。

结合实时开发者通知

如果服务端已经接入 Real-time Developer Notifications,可以在收到订阅变化通知后重新查询 Google Play Developer API,并更新本地订阅记录。通知可以用来触发同步,但不能只根据通知内容推断最终状态。

对于题目中的 REST API 字段,正确的处理方式不是在 Billing Library 中寻找等价字段,而是由服务端查询并判断价格变更状态。Billing Library 负责客户端购买交互,以及在受支持版本中启动确认流程。

备注:内容仅供参考。