PAYATHON 2026

何时会广播 com.android.vending.billing.PURCHASES_UPDATED?

支付老李

结论

com.android.vending.billing.PURCHASES_UPDATED 是旧版 Google Play Billing 机制中的通知广播。当 Google Play 发现本机当前应用的购买记录发生变化时,可能会发送这个广播。常见情况包括购买完成、购买被取消或退款,以及购买状态同步后发生变化。

这个广播只说明购买数据可能已经变化,并不能可靠地指出具体订单或变化内容。收到广播后,应用应重新调用 Billing API 查询购买记录并核对状态,不能将广播本身视为购买凭证。

新功能应使用 Google Play Billing Library 的 PurchasesUpdatedListener 和 queryPurchasesAsync(),不应再把这个广播作为核心业务入口。

广播通常在什么情况下出现

使用旧版 In-app Billing API 时,以下情况可能触发该广播:

  • 用户完成应用内购买。
  • Google Play 将处于等待状态的交易更新为成功或失败。
  • 购买被取消、撤销或退款,且状态已经同步到设备。
  • Google Play 重新同步设备上的购买记录,并发现当前应用的购买数据发生变化。
  • 用户通过应用外部的 Play 流程完成购买,随后记录同步到当前设备。

需要注意的是,这些情况只是可能触发广播。该 action 属于旧版 Billing 工作流,Google 并未承诺它能覆盖所有购买状态变化,也不保证每次变化都会立即发送广播。应用未运行、设备离线、Play 商店版本不同以及系统后台限制,都可能影响接收时间。

跨设备购买和服务端退款尤其不应依赖本地广播来及时发现。如果业务对可靠性要求较高,应同时使用服务端校验和 Google Play Real-time Developer Notifications。

收到广播后应该怎么做

处理流程如下:

  1. Receiver 收到 PURCHASES_UPDATED。
  2. 不读取或信任广播中没有明确约定的额外字段。
  3. 通过 Billing API 重新查询当前购买记录。
  4. 根据 purchaseToken、商品 ID 和购买状态更新业务数据。
  5. 对有效购买执行服务端验证、确认或消耗操作。
  6. 确保处理逻辑幂等,避免重复发放权益。

广播只是一个刷新信号。客户端应以查询结果作为购买状态的依据。

旧版 Receiver 示例

如果旧项目仍依赖该 action,可以定义一个 Receiver,只用它触发购买查询:

public final class PurchasesUpdatedReceiver extends BroadcastReceiver {

    public static final String ACTION_PURCHASES_UPDATED =
            "com.android.vending.billing.PURCHASES_UPDATED";

    private final Runnable refreshPurchases;

    public PurchasesUpdatedReceiver(Runnable refreshPurchases) {
        this.refreshPurchases = refreshPurchases;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (ACTION_PURCHASES_UPDATED.equals(intent.getAction())) {
            refreshPurchases.run();
        }
    }
}

动态注册示例:

private PurchasesUpdatedReceiver purchasesReceiver;

@Override
protected void onStart() {
    super.onStart();

    purchasesReceiver =
            new PurchasesUpdatedReceiver(this::queryCurrentPurchases);

    IntentFilter filter = new IntentFilter(
            PurchasesUpdatedReceiver.ACTION_PURCHASES_UPDATED
    );

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        registerReceiver(
                purchasesReceiver,
                filter,
                Context.RECEIVER_EXPORTED
        );
    } else {
        registerReceiver(purchasesReceiver, filter);
    }
}

@Override
protected void onStop() {
    unregisterReceiver(purchasesReceiver);
    purchasesReceiver = null;
    super.onStop();
}

由于广播来自应用进程之外,在 Android 13 及以上版本中动态注册时,需要根据实际发送方式正确设置 Receiver 标志。旧项目能否继续收到该广播,还会受到 Billing 实现、Google Play 商店版本和系统限制的影响,因此需要在目标设备和目标版本上验证。

使用当前 Billing Library 的实现

新代码应直接监听购买流程的结果:

PurchasesUpdatedListener purchasesUpdatedListener =
        (billingResult, purchases) -> {
            if (billingResult.getResponseCode()
                    == BillingClient.BillingResponseCode.OK
                    && purchases != null) {
                for (Purchase purchase : purchases) {
                    handlePurchase(purchase);
                }
            } else if (billingResult.getResponseCode()
                    == BillingClient.BillingResponseCode.USER_CANCELED) {
                // 用户取消购买
            } else {
                // 记录或处理其他错误
            }
        };

建立连接后,还要主动查询已有购买,以覆盖应用重启、重新回到前台或回调未到达等情况:

QueryPurchasesParams params = QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.INAPP)
        .build();

billingClient.queryPurchasesAsync(
        params,
        (billingResult, purchases) -> {
            if (billingResult.getResponseCode()
                    == BillingClient.BillingResponseCode.OK) {
                for (Purchase purchase : purchases) {
                    handlePurchase(purchase);
                }
            }
        }
);

查询订阅商品时,需要另外使用:

BillingClient.ProductType.SUBS

PurchasesUpdatedListener 主要接收当前 Billing 流程的结果,queryPurchasesAsync() 则用于恢复和同步购买记录,两者不能互相替代。

注意事项

  • 收到广播后,不要直接发放商品或会员权益。
  • 广播可能重复发送,购买处理必须基于 purchaseToken 实现幂等控制。
  • 客户端结果可能被篡改,重要权益应通过服务端验证购买。
  • PURCHASED、PENDING 等状态需要分别处理。购买处于 PENDING 状态时,不能提前发放权益。
  • 需要确认的购买应及时调用 acknowledgePurchase();消耗型商品则按业务要求调用 consumeAsync()。
  • 退款、撤销和跨设备状态变化不适合仅通过本地 Receiver 追踪。
  • 不要假设广播一定包含订单号、商品 ID 或最终购买状态。
  • 如果某个旧 SDK 明确要求注册该 Receiver,可以将其作为 SDK 的兼容入口,但仍需保留应用启动和恢复前台时主动查询购买记录的逻辑。

备注:内容仅供参考。