Android In-App Billing (Subscription) 如何设置免付费用户?
明确结论
Google Play Billing API 不支持将正常订阅订单的价格针对指定用户改为 0,用户完成订阅后也不能跳过 Google Play 收款。
如果想让自己、家人或朋友永久免费使用 premium 功能,可以通过应用自身的账号和权限系统,为这些用户直接授予 premium 权限,无需创建 Google Play 订阅。
推荐实现方式
将“是否为 premium 用户”与“是否拥有有效订阅”分开判断。用户满足以下任一条件,就可以获得 premium 权限:
- 拥有经过验证且仍然有效的 Google Play 订阅;
- 由后台管理员手动授予 premium 权限;
- 获得设有明确到期时间的赠送权限。
服务端可以保存权限来源:
user_id
premium_source // google_play、manual_grant、promotion
premium_expires_at // 永久授权可为空
revoked_at
note
判断逻辑可以写成:
premium =
hasActiveGooglePlaySubscription(userId)
OR hasValidManualGrant(userId)
正常用户仍通过 Google Play 购买订阅,指定用户则由应用后台直接授权。
Kotlin 示例
客户端不应只根据本地名单判断权限。用户登录后,客户端应向服务端查询:
data class Entitlement(
val premium: Boolean,
val source: String?,
val expiresAt: String?
)
suspend fun refreshPremiumStatus(userId: String): Boolean {
val entitlement = entitlementApi.getEntitlement(userId)
return entitlement.premium
}
服务端可以采用类似的判断方式:
fun hasPremiumAccess(userId: String): Boolean {
val manualGrant = premiumGrantRepository.findActiveGrant(userId)
if (manualGrant != null &&
manualGrant.revokedAt == null &&
(manualGrant.expiresAt == null || manualGrant.expiresAt.isAfter(Instant.now()))
) {
return true
}
return subscriptionRepository.hasActiveGooglePlaySubscription(userId)
}
管理员为指定账号授权时,可以同时记录权限来源和授权原因:
fun grantPremium(
userId: String,
expiresAt: Instant? = null,
note: String? = null
) {
premiumGrantRepository.save(
PremiumGrant(
userId = userId,
source = "manual_grant",
expiresAt = expiresAt,
revokedAt = null,
note = note
)
)
}
在生产环境中,这类管理接口必须验证管理员身份,并记录审计日志。
Google Play 订阅仍需正常验证
对于付费用户,服务端应验证其购买凭据。客户端通过 Google Play Billing Library 获取 purchaseToken,将它发送给服务端,再由服务端调用 Google Play Developer API 查询订阅状态。
不要只依赖客户端的以下判断:
purchase.purchaseState == Purchase.PurchaseState.PURCHASED
客户端状态可能被篡改,订阅也可能续订失败、被退款、被撤销或已经过期。因此,最终的 premium 权限应由服务端结合订阅状态和手动授权状态统一判断。
其他可选方案
促销代码或优惠
在部分产品配置、地区和政策条件下,Google Play Console 可能提供订阅促销代码、免费试用或优惠方案。这些功能适合营销活动,但通常会受到数量、有效期、用户资格和兑换规则等限制。
这类优惠并不等于“让任意指定账号永久免除订阅费”。具体能否使用,应以 Play Console 当前为该订阅产品提供的选项及 Google Play 官方政策为准。
License testers
License testers 适合测试购买流程,不适合用作亲友长期免费使用 premium 的正式方案。测试购买可能采用特殊的测试周期和结算方式,不应作为生产环境长期授予权限的依据。
本地白名单
从技术上看,可以在客户端加入白名单:
val freePremiumUsers = setOf(
"friend@example.com",
"family@example.com"
)
但不建议这样处理。名单会被打包进安装包,存在被反编译的风险;每次修改名单还要重新发布应用,仅检查邮箱字符串也容易被绕过。更稳妥的做法是把白名单保存在受控的服务端,并使用稳定的内部 userId 建立关联,不要依赖昵称或可能变更的邮箱。
注意事项
- 应用需要有自己的登录账号体系,才能可靠识别获得赠送权限的用户。
- 不要用设备 ID 识别亲友。换机、重装和隐私限制都会影响识别结果。
- 手动授权最好支持设置到期时间、撤销权限和审计记录,避免将永久权限写死。
- 购买、退款或订阅状态发生变化时,应及时同步服务端权限。
- 如果 premium 数字功能通常必须通过 Google Play Billing 销售,就不能把手动赠送权限包装成绕过 Google Play 支付的公开销售渠道。
- 少量内部测试人员可以使用测试机制;需要长期免付费使用时,应采用服务端授权。
备注:内容仅供参考。
