PAYATHON 2026

如何通过 Billing Library 判断用户是否符合 Free Trial 资格?

数据小陈

结论

Billing Library 不提供“用户是否使用过 Free Trial”的独立布尔值,也不能查询某个 Google Play 账号的试用历史。

较可靠的判断方式是调用 queryProductDetailsAsync(),查询当前 Google Play 账号可以购买的订阅方案,再检查返回的 subscriptionOfferDetails 中是否有包含免费试用阶段的 offer。Google Play 会根据订阅后台配置和当前账号的资格,筛选可用的 offer。

因此,UI 应表述为“当前可购买方案包含免费试用”,不能声称已经确认“用户从未使用过试用”。用户最终是否符合资格,仍以 Google Play 结算界面为准。

判断流程

  1. 在 Google Play Console 中为订阅配置免费试用 offer。
  2. 按业务需要设置资格条件,例如仅允许从未订阅过该应用订阅的用户参加。
  3. App 调用 queryProductDetailsAsync() 获取当前可用的订阅方案。
  4. 在返回的 subscriptionOfferDetails 中查找对应的 offerId 或 offerTags。
  5. 检查该 offer 的 pricingPhases,确认其中存在价格为零的阶段。
  6. 如果找到,显示“可免费试用”,并在发起购买时使用该 offer 的 offerToken。
  7. 如果没有找到,展示正常订阅价格,不要直接断言用户“已经使用过试用”。

Kotlin 示例

以下示例假设免费试用 offer 配置了 free-trial 标签。用固定的 offerId 或 offerTags 识别试用方案,通常比只检查价格是否为零更稳妥。

private const val SUBSCRIPTION_ID = "premium_subscription"
private const val FREE_TRIAL_TAG = "free-trial"

data class TrialOffer(
    val productDetails: ProductDetails,
    val offerDetails: ProductDetails.SubscriptionOfferDetails
)

fun queryFreeTrialOffer(
    billingClient: BillingClient,
    onResult: (TrialOffer?) -> Unit
) {
    val product = QueryProductDetailsParams.Product.newBuilder()
        .setProductId(SUBSCRIPTION_ID)
        .setProductType(BillingClient.ProductType.SUBS)
        .build()

    val params = QueryProductDetailsParams.newBuilder()
        .setProductList(listOf(product))
        .build()

    billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
        if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
            onResult(null)
            return@queryProductDetailsAsync
        }

        val productDetails = productDetailsList
            .firstOrNull { it.productId == SUBSCRIPTION_ID }

        val trialOffer = productDetails
            ?.subscriptionOfferDetails
            .orEmpty()
            .firstOrNull { offer ->
                val isConfiguredTrialOffer =
                    FREE_TRIAL_TAG in offer.offerTags

                val containsZeroPricePhase =
                    offer.pricingPhases.pricingPhaseList.any { phase ->
                        phase.priceAmountMicros == 0L
                    }

                isConfiguredTrialOffer && containsZeroPricePhase
            }

        onResult(
            if (productDetails != null && trialOffer != null) {
                TrialOffer(productDetails, trialOffer)
            } else {
                null
            }
        )
    }
}

UI 可以根据查询结果显示不同内容:

queryFreeTrialOffer(billingClient) { trialOffer ->
    if (trialOffer != null) {
        showSubscriptionMessage("可免费试用")
        enableTrialPurchase(trialOffer)
    } else {
        showSubscriptionMessage("查看订阅价格")
        showRegularSubscriptionOptions()
    }
}

发起购买时,必须使用查询结果中的 offerToken:

fun launchTrialPurchase(
    activity: Activity,
    billingClient: BillingClient,
    trialOffer: TrialOffer
): BillingResult {
    val productDetailsParams =
        BillingFlowParams.ProductDetailsParams.newBuilder()
            .setProductDetails(trialOffer.productDetails)
            .setOfferToken(trialOffer.offerDetails.offerToken)
            .build()

    val billingFlowParams = BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(listOf(productDetailsParams))
        .build()

    return billingClient.launchBillingFlow(activity, billingFlowParams)
}

具体的回调类型和 ProductDetails 结果包装形式可能会随 Billing Library 版本变化,应以项目当前依赖版本的 API 签名为准。判断原则不受影响。

为什么不能直接查询“是否用过试用”

Google Play 会结合购买账号、订阅配置和 offer 条件计算试用资格。应用通常无法获得以下信息:

  • 当前 Google Play 账号完整的订阅试用历史;
  • 明确表示“此账号曾经使用过该试用”的字段;
  • 用户在设备上切换 Google Play 账号的可靠通知;
  • 能够用来自行复制 Google Play 资格规则的稳定账号标识。

queryPurchasesAsync() 只能查询当前有效购买等购买状态,无法证明用户过去从未参加过试用。对于已经取消、到期或通过另一个应用账号完成的购买,也不能靠它完整判断历史情况。

旧接口中的 SkuDetails.freeTrialPeriod 也只能说明商品配置了试用期,不能证明当前用户符合试用资格。

不要只依赖服务器记录

服务器应保存购买凭据、订阅状态,以及它们与应用账号之间的绑定关系,用于授权和防止重复发放权益。但服务器不能代替 Google Play 判断试用资格,原因包括:

  • 一个应用账号可能对应多个 Google Play 账号;
  • 多个应用账号可能使用同一个 Google Play 账号;
  • 用户可能在购买前切换 Google Play 账号;
  • Google Play 的资格规则可能会使用服务器无法获得的购买历史。

如果 offer 配置为由开发者自行判断资格,就需要通过自己的服务器完成资格判断,同时处理应用账号切换、重复注册和权益滥用等风险。这与 Google Play 自动判断的新用户 offer 属于不同的机制。

UI 和购买流程的注意事项

查到免费试用 offer 后,不要长期缓存结果。账号、订阅配置和资格都可能变化,建议在用户进入订阅页或点击购买前重新查询。

即使 App 显示“可免费试用”,提交购买时仍应以 Google Play 结算界面显示的价格和条款为准。查询失败时,也不能默认用户符合资格。此时可以显示“正在获取订阅方案”,或者只提供通用订阅入口。

建议按以下方式展示:

  • 查到符合预期的试用 offer:显示具体的试用信息;
  • 只查到普通方案:显示普通订阅价格;
  • 查询失败:显示加载或重试状态;
  • 不显示“你已经使用过免费试用”。缺少试用 offer 也可能与地区、商品配置、账号状态或临时查询问题有关。

备注:内容仅供参考。