Google Play In-App Billing 如何实现一次性购买的 7 天试用
明确结论
Google Play In-App Billing 不支持为一次性购买的非消耗型商品设置原生免费试用。订阅商品可以配置免费试用,但一次性商品无法直接实现“前 7 天免费,试用结束后恢复受限功能”。
这部分逻辑需要由应用自己管理:
- 用户首次使用时记录试用开始时间。
- 在 7 天试用期内开放完整功能。
- 试用结束后恢复受限功能。
- 用户购买一次性商品后永久解锁。
不使用服务端也可以实现,但用户清除应用数据、重新安装、修改系统时间或篡改应用后,可能再次获得试用资格。
推荐的状态设计
“正在试用”和“已经购买”是两种不同的状态,不应混在一起。应用至少需要区分以下三种状态:
PURCHASED 已购买,永久开放完整功能
TRIAL_ACTIVE 仍在 7 天试用期内
LIMITED 未购买且试用已结束
判断时应先检查购买状态,再检查试用状态:
已拥有一次性商品?
├─ 是:PURCHASED
└─ 否:试用是否仍有效?
├─ 是:TRIAL_ACTIVE
└─ 否:LIMITED
购买状态通过 Google Play Billing 查询,试用状态则由应用自己的逻辑计算。
本地实现示例
下面的 Kotlin 示例从用户第一次启动应用时开始计算 7 天试用期:
import android.content.Context
class TrialManager(context: Context) {
private val preferences = context.getSharedPreferences(
"trial_preferences",
Context.MODE_PRIVATE
)
companion object {
private const val KEY_TRIAL_START_TIME = "trial_start_time"
private const val KEY_LATEST_KNOWN_TIME = "latest_known_time"
private const val TRIAL_DURATION_MILLIS = 7L * 24 * 60 * 60 * 1000
}
fun initializeTrialIfNeeded() {
if (!preferences.contains(KEY_TRIAL_START_TIME)) {
val now = System.currentTimeMillis()
preferences.edit()
.putLong(KEY_TRIAL_START_TIME, now)
.putLong(KEY_LATEST_KNOWN_TIME, now)
.apply()
}
}
fun isTrialActive(): Boolean {
val startTime = preferences.getLong(KEY_TRIAL_START_TIME, -1L)
if (startTime < 0L) {
return false
}
val currentTime = System.currentTimeMillis()
val latestKnownTime = preferences.getLong(
KEY_LATEST_KNOWN_TIME,
startTime
)
// 检测明显的系统时间回拨。
if (currentTime < latestKnownTime) {
return false
}
preferences.edit()
.putLong(KEY_LATEST_KNOWN_TIME, currentTime)
.apply()
return currentTime - startTime < TRIAL_DURATION_MILLIS
}
fun remainingTrialMillis(): Long {
val startTime = preferences.getLong(KEY_TRIAL_START_TIME, -1L)
if (startTime < 0L) {
return 0L
}
val elapsed = System.currentTimeMillis() - startTime
return (TRIAL_DURATION_MILLIS - elapsed).coerceAtLeast(0L)
}
}
应用启动时初始化试用:
val trialManager = TrialManager(applicationContext)
trialManager.initializeTrialIfNeeded()
然后结合购买状态决定用户可以使用哪些功能:
enum class AccessState {
PURCHASED,
TRIAL_ACTIVE,
LIMITED
}
fun resolveAccessState(
ownsFullVersion: Boolean,
trialManager: TrialManager
): AccessState {
return when {
ownsFullVersion -> AccessState.PURCHASED
trialManager.isTrialActive() -> AccessState.TRIAL_ACTIVE
else -> AccessState.LIMITED
}
}
使用功能时可以统一判断:
when (resolveAccessState(ownsFullVersion, trialManager)) {
AccessState.PURCHASED,
AccessState.TRIAL_ACTIVE -> enableFullFeatures()
AccessState.LIMITED -> enableLimitedFeatures()
}
ownsFullVersion 不能只读取本地布尔值,而应通过 Google Play Billing 查询用户当前拥有的商品。
查询一次性商品的购买状态
查询一次性商品的已有购买时,应使用 ProductType.INAPP:
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.QueryPurchasesParams
fun queryFullVersionPurchase(
billingClient: BillingClient,
productId: String,
onResult: (Boolean) -> Unit
) {
val params = QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.INAPP)
.build()
billingClient.queryPurchasesAsync(params) { billingResult, purchases ->
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
onResult(false)
return@queryPurchasesAsync
}
val ownsProduct = purchases.any { purchase ->
purchase.purchaseState ==
com.android.billingclient.api.Purchase.PurchaseState.PURCHASED &&
productId in purchase.products
}
onResult(ownsProduct)
}
}
实际项目还需要按照 Google Play Billing 的要求确认购买,也就是对尚未确认的购买调用 acknowledgement API。应用不能仅凭购买回调永久解锁功能,也不应只把“已购买”状态写入 SharedPreferences。
Billing Library 的初始化方式和部分接口写法可能随版本变化,具体实现应以项目当前依赖版本的官方文档为准。整体判断逻辑不受影响:购买系统管理永久权益,应用管理试用期限。
不使用服务端的限制
纯本地方案比较容易实现,但无法可靠保证“每个用户只能试用一次”,原因包括:
- 清除应用数据会删除
SharedPreferences。 - 卸载后重新安装通常会丢失本地试用记录。
- 用户可能回拨系统时间。
- Root、备份恢复或应用篡改可能绕过检查。
- 同一个 Google 账号在多台设备上的试用状态无法同步。
- 本地购买状态可能暂时无法查询,因此不能简单地把查询失败当作未购买。
记录 latestKnownTime 只能拦截一部分系统时间回拨行为,无法让纯本地方案变得真正安全。
需要严格限制试用次数时
如果业务要求“同一账号只能试用一次”,应用就需要使用可信的远端状态。常见流程如下:
- 用户登录应用账号。
- 服务端保存
trialStartedAt和trialEndsAt。 - 应用从服务端获取当前权益状态。
- 服务端验证 Google Play 购买,并把永久购买权益绑定到用户。
- 必要时结合 Play Integrity API,提高滥用成本。
服务端只需保存少量权益数据,不一定要实现复杂的订阅系统。试用记录可以使用类似下面的结构:
{
"userId": "12345",
"trialStartedAt": "2026-09-13T08:00:00Z",
"trialEndsAt": "2026-09-20T08:00:00Z",
"ownsFullVersion": false
}
如果应用不要求用户登录,也可以为设备生成安装标识,但这种方式仍然无法可靠阻止换设备、清除数据或重新安装。
其他实现方式是否合适
Google Play 原生支持订阅商品的免费试用,但试用结束后通常会进入订阅计费流程。这与“试用结束后,由用户自行决定是否购买永久版”的模式不同,因此不应为了使用免费试用功能,把永久解锁商品设计成订阅。
即使目标市场和当前 Play Console 支持一次性商品的优惠、租赁或其他销售配置,它们也不等同于“不发生购买交易的 7 天免费试用”,无法代替应用自己的试用状态管理。
实际建议
如果应用价值不高,也能接受试用被重复使用的风险,本地计时 7 天通常已经足够。界面中最好明确显示剩余试用时间。
如果一次性购买价格较高,或者试用资格会直接影响收入,则应由服务端保存试用状态并验证购买。Google Play Billing 可以可靠管理一次性购买权益,但不能单独管理非消耗型商品的免费试用。
备注:内容仅供参考。