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

Android Google Play Billing(v5):新购订阅后旧订阅未自动终止问题

Google Play 订阅升级(月度转年度)的正确处理方案

一、理想情况下旧订阅的处理逻辑

当用户从生效的月度订阅转为年度订阅时,必须使用Google Play官方的订阅升级/降级机制,而非让用户购买新的独立订阅。理想处理分为两种模式:

  • 立即生效模式:用户即刻获得年度订阅权益,剩余的月度订阅时长按比例折算为年度时长(或按规则退还差价),旧月度订阅自动终止,不会触发后续自动续费。
  • 到期生效模式:旧月度订阅继续生效至当前周期结束,到期后自动切换为年度订阅,旧订阅自动取消,不会产生续费扣费。

如果出现旧订阅未自动取消、到期后仍续费的情况,说明你未使用官方的订阅更新API,而是让用户购买了独立的新订阅,导致两个订阅共存。

二、实现思路与代码片段(基于Google Play Billing Library 5.x)

核心逻辑是通过订阅更新API,将用户当前的月度订阅直接转为年度订阅,而非创建新订阅。需确保月度和年度订阅属于同一个订阅组(在Google Play Console中配置)。

1. 依赖配置

在build.gradle中引入Billing Library:

dependencies {
    implementation "com.android.billingclient:billing-ktx:5.2.0"
}

2. 查询用户当前活跃订阅

先获取用户已购买的月度订阅信息:

private fun queryActiveMonthlySubscription() {
    val queryParams = QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .build()

    billingClient.queryPurchasesAsync(queryParams) { billingResult, purchases ->
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases.isNotEmpty()) {
            // 筛选出用户活跃的月度订阅
            val monthlyPurchase = purchases.firstOrNull { it.skus.contains("your_monthly_sub_sku") }
            monthlyPurchase?.let {
                upgradeToAnnualSubscription(it)
            }
        }
    }
}

3. 发起订阅升级请求

构建更新参数,指定转年度的规则:

private fun upgradeToAnnualSubscription(currentPurchase: Purchase) {
    val annualSku = "your_annual_sub_sku" // 替换为你的年度订阅SKU
    val annualSkuDetails = getCachedAnnualSkuDetails() // 需提前通过querySkuAsync获取并缓存SKU详情

    val updateParams = BillingFlowParams.SubscriptionUpdateParams.newBuilder()
        .setOldPurchaseToken(currentPurchase.purchaseToken)
        // 选择计费模式:立即生效并折算剩余时长
        .setProrationMode(BillingClient.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION)
        // 若要到期生效,替换为:BillingClient.ProrationMode.DEFERRED
        .build()

    val billingFlowParams = BillingFlowParams.newBuilder()
        .setSubscriptionUpdateParams(updateParams)
        .setSkuDetailsList(listOf(annualSkuDetails))
        .build()

    // 启动Google Play计费流程
    billingClient.launchBillingFlow(activity, billingFlowParams)
}

// 示例:获取缓存的年度SKU详情(需提前查询)
private fun getCachedAnnualSkuDetails(): SkuDetails {
    // 实际开发中需通过querySkuAsync查询所有订阅SKU并缓存,此处省略查询逻辑
    return yourCachedAnnualSkuDetails
}

4. 处理订阅更新结果

在PurchasesUpdatedListener中处理升级后的结果:

private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
        for (purchase in purchases) {
            // 验证订阅合法性,更新用户本地订阅状态
            verifyAndUpdateSubscriptionStatus(purchase)
        }
    } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
        // 用户取消升级流程,无需额外操作
    } else {
        // 处理其他错误,比如网络问题、权限不足等
    }
}

关键注意事项

  • 必须将月度和年度订阅配置到同一个订阅组(Google Play Console > 应用内产品 > 订阅 > 订阅组),否则无法进行升级/降级操作。
  • 计费模式(ProrationMode)可根据业务需求选择,常见选项:
    • IMMEDIATE_WITH_TIME_PRORATION:立即生效,剩余月度时长折算为年度时长,无重复扣费。
    • DEFERRED:当前月度订阅到期后自动切换为年度,旧订阅自动终止。
    • IMMEDIATE_AND_CHARGE_PRORATED_PRICE:立即生效,收取年度与剩余月度的差价。

内容的提问来源于stack exchange,提问作者Nitish Verma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 12:05:01