如何在 Google Play Store 实现整款 Android 应用订阅
结论
Google Play 没有单独的“整款应用订阅”商品类型。用户按周期付费获得应用的数字使用权时,应将这项权益配置为 Google Play Billing 的 subscription,并在应用内发起购买。
你看到的 in-app subscription 文档就是需要实现的流程。即使应用只有一个订阅项目,订阅后会解锁全部功能,在 Google Play 的计费模型中也仍属于应用内订阅。
常见的产品流程如下:
- 用户免费下载并安装应用。
- 打开应用后,用户会看到登录页、试用内容或订阅页。
- 应用通过 Google Play Billing 发起订阅。
- 购买成功并通过验证后,应用解锁全部功能。
- 订阅失效、退款或被撤销后,应用收回相应权益。
Google Play Console 用于管理商品、价格和订单。当前用户能否使用完整功能,则由应用或服务器判断。
需要实现的内容
按照 Google Play Billing 的 subscription 流程实现即可,无须另找“应用级订阅”接口。旧文档中的 AIDL、SkuDetails、skuId 和 SkuType.SUBS 属于旧接口体系。新项目应使用当前受支持的 Google Play Billing Library,以及 ProductDetails、BillingClient.ProductType.SUBS、base plan 和 offer 等模型。
主要需要完成以下环节:
- 在 Play Console 中创建 subscription product。
- 为该商品创建按月自动续订的 base plan。
- 在应用中查询
ProductDetails。 - 从
subscriptionOfferDetails获取offerToken。 - 使用
launchBillingFlow()发起购买。 - 处理
PurchasesUpdatedListener返回的购买结果。 - 验证
purchaseToken。 - 对首次购买执行 acknowledgement。
- 在应用启动、用户登录和应用恢复前台时重新检查订阅权益。
- 处理续订、取消、退款、宽限期、账号保留和撤销等状态。
一次性商品的消费流程和 consumeAsync() 等逻辑不适用于订阅,无须实现。
在 Play Console 中配置订阅
Play Console 的菜单名称可能随界面更新而变化。订阅配置一般位于 Monetize、Products 或 Subscriptions 相关页面。配置步骤通常包括:
- 配置开发者付款资料和商家信息。
- 创建订阅商品,例如
full_access。 - 创建按月自动续订的 base plan。
- 设置价格和销售国家或地区,然后激活 base plan。
- 上传包含 Billing Library 的应用版本。
- 通过 internal testing 或 closed testing 测试订阅。
- 配置 license testers,避免测试时生成普通的正式订单。
商品 ID 发布后不宜随意更改。免费试用和首期优惠等内容应通过 offer 配置,无须另建一套本地计费逻辑。
在应用内发起订阅
以下代码展示了如何使用 ProductDetails 查询订阅并启动购买。不同版本 Billing Library 的回调签名可能略有不同,应以项目实际采用版本的官方 API 为准。
private const val PRODUCT_ID = "full_access"
fun querySubscription(
billingClient: BillingClient,
onLoaded: (ProductDetails) -> Unit
) {
val product = QueryProductDetailsParams.Product.newBuilder()
.setProductId(PRODUCT_ID)
.setProductType(BillingClient.ProductType.SUBS)
.build()
val params = QueryProductDetailsParams.newBuilder()
.setProductList(listOf(product))
.build()
billingClient.queryProductDetailsAsync(params) { billingResult, result ->
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
return@queryProductDetailsAsync
}
result.productDetailsList.firstOrNull()?.let(onLoaded)
}
}
启动购买时,必须使用该用户实际可以购买的 offer 所对应的 offerToken:
fun launchSubscriptionPurchase(
activity: Activity,
billingClient: BillingClient,
productDetails: ProductDetails
) {
val offer = productDetails.subscriptionOfferDetails
?.firstOrNull()
?: return
val productParams =
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offer.offerToken)
.build()
val billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productParams))
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
}
实际项目中不能无条件选择第一个 offer。配置了免费试用、用户资格限制或多种价格方案时,需要根据 basePlanId、offerId 和定价阶段选择合适的 offerToken。
购买结果由 PurchasesUpdatedListener 接收:
private val purchasesUpdatedListener =
PurchasesUpdatedListener { billingResult, purchases ->
if (billingResult.responseCode ==
BillingClient.BillingResponseCode.OK
) {
purchases.orEmpty().forEach { purchase ->
when (purchase.purchaseState) {
Purchase.PurchaseState.PURCHASED -> {
sendPurchaseTokenToServer(
productId = PRODUCT_ID,
purchaseToken = purchase.purchaseToken
)
}
Purchase.PurchaseState.PENDING -> {
showPendingMessage()
}
else -> Unit
}
}
}
}
收到 PURCHASED 只表示客户端已取得购买记录,不能仅凭这次回调或本地保存的布尔值永久解锁功能。PENDING 状态也不能授予权益,必须等购买状态变为 PURCHASED。
是否需要搭建后端
开发者可以在 Play Console 中查看商品、订单和财务数据,但 Play Console 不会替应用实时判断某个登录用户是否仍有订阅权益。正式上线的应用通常应配置后端,尤其是带有账号系统、云端数据或防篡改要求的应用。
可以采用以下处理流程:
Android 应用
-> 将 purchaseToken 发送给业务服务器
-> 服务器调用 Google Play Developer API 验证订阅
-> 服务器保存订阅状态和到期时间
-> 应用向业务服务器查询当前权益
服务端可以使用 Google Play Developer API 的 purchases.subscriptionsv2.get 查询订阅状态。首次购买验证成功后,还要在规定期限内完成 acknowledgement,否则订单可能被自动退款。客户端和服务器都可以执行 acknowledgement,每个订单成功处理一次即可。
同时建议接入 Real-time Developer Notifications。服务器收到续订、取消、退款或撤销通知后,再调用 Developer API 获取完整状态。通知只能说明订阅状态可能发生了变化,不能直接作为授予权益的依据。
对于规模很小的应用,也可以先在客户端通过 queryPurchasesAsync() 查询当前 Google Play 账号拥有的订阅,并在设备端完成 acknowledgement。不过,这种实现容易受到客户端篡改、本地状态过期和账号映射不清等问题影响,不适合长期用于生产环境。
判断权益时需要处理的状态
用户取消订阅后,通常仍可使用到当前计费周期结束。应用需要根据 Google 返回的有效期和订阅状态判断权益,不能在看到取消标志后立即锁定功能。
各状态可按以下方式处理:
PENDING:付款尚未完成,不授予权益。- 正常续订:继续保留权益。
- 用户取消:一般保留权益至当前有效期结束。
- grace period:是否继续提供权益取决于订阅配置和业务规则。
- account hold:付款失败并进入保留状态,需要根据实际订阅状态处理。
- paused:暂停期间通常不再授予订阅权益。
- refunded 或 revoked:及时收回权益。
- expired:订阅到期后关闭完整功能。
权益检查不能只在购买当天执行。应用冷启动、用户登录、恢复前台或收到服务端状态变更时,都应重新确认权益。
单个订阅项目也要支持恢复
用户可能会重新安装应用、更换设备,或在另一台设备上登录,因此不能只在本地偏好设置中保存“已订阅”状态。
没有业务账号系统时,可以查询当前 Google Play 账号的有效购买记录。有业务账号系统时,应在服务器上将验证通过的 purchaseToken 与业务用户绑定。购买参数中的 obfuscatedAccountId 可以辅助账号关联和风控,但其中不能包含邮箱、手机号等明文个人信息。
上线前检查
发布前至少确认以下事项:
- 订阅商品和 base plan 已激活。
- 测试版本的 package name 与 Play Console 一致。
- 测试账号已加入测试轨道并接受测试邀请。
- 应用通过 Google Play 安装,不是只用本地 APK 测试。
- 页面展示的价格来自
ProductDetails,客户端没有写死价格。 - 购买成功后已验证
purchaseToken。 - 首次购买已完成 acknowledgement。
PENDING订单不会提前解锁功能。- 重装应用或更换设备后可以恢复权益。
- 取消、到期、退款和撤销后能够正确更新访问权限。
- 订阅页面清楚说明价格、计费周期、自动续订规则和管理方式。
这个需求可以配置为“一项解锁整款应用的 subscription product”。它使用标准的应用内订阅流程,订阅授予的权益覆盖整个应用。
备注:内容仅供参考。
