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

iOS应用内购开发疑问:如何判断用户是否已购买指定内购项目?

确认用户内购项目购买状态的可行方案

嘿,这个需求在做内购功能时太常见了!我来给你梳理几个iOS和Android平台都适用的成熟实现方案,确保你能准确判断用户是否已购买非消耗型或自动续期订阅项目:

iOS(App Store)平台方案

1. 利用StoreKit直接查询交易状态

  • 如果你用的是StoreKit 2(iOS 15+),可以通过异步API直接获取用户的交易记录,代码非常简洁:
    import StoreKit
    
    func checkPurchasedStatus() async {
        do {
            // 获取所有关联的交易记录
            let transactions = try await Transaction.all()
            
            for transaction in transactions {
                guard let productID = transaction.productID else { continue }
                
                switch transaction.transactionState {
                case .purchased, .restored:
                    // 标记该产品为已购买(可以存到UserDefaults或本地数据库)
                    UserDefaults.standard.set(true, forKey: "Purchased_\(productID)")
                case .expired, .revoked:
                    // 订阅过期或被退款,标记为未购买
                    UserDefaults.standard.set(false, forKey: "Purchased_\(productID)")
                default:
                    break
                }
            }
        } catch {
            print("查询购买状态出错:\(error.localizedDescription)")
        }
    }
    
  • 对于iOS 14及以下版本,可以用SKReceiptRefreshRequest刷新凭证后,解析本地凭证文件来判断购买状态。

2. 服务器端验证(必做,提升安全性)

本地验证容易被篡改,所以强烈建议把设备上的交易凭证发送到你的后端服务器,后端调用App Store官方验证接口,验证通过后再把结果返回给客户端。这样能有效防止破解和伪造购买状态。

3. 处理订阅的特殊状态

自动续期订阅需要额外检查:

  • 用StoreKit 2的SubscriptionStatus可以直接获取订阅的当前状态(活跃、过期、待确认等)
  • 定期刷新订阅状态,因为用户可能关闭自动续订、更换订阅方案或申请退款

Android(Google Play)平台方案

1. 使用Google Play Billing Library查询购买记录

借助Billing Library(推荐v5+),可以异步查询用户已购买的非消耗型产品和订阅:

import com.android.billingclient.api.*

fun checkPurchasedProducts(context: Context) {
    val billingClient = BillingClient.newBuilder(context)
        .setListener { billingResult, purchases ->
            // 实时处理购买更新事件
            purchases?.let { updatePurchasedStatus(it) }
        }
        .enablePendingPurchases()
        .build()

    billingClient.startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(result: BillingResult) {
            if (result.responseCode == BillingClient.BillingResponseCode.OK) {
                // 查询非消耗型产品
                val inAppParams = QueryPurchasesParams.newBuilder()
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build()
                billingClient.queryPurchasesAsync(inAppParams) { _, purchases ->
                    updatePurchasedStatus(purchases)
                }

                // 查询订阅产品
                val subParams = QueryPurchasesParams.newBuilder()
                    .setProductType(BillingClient.ProductType.SUBS)
                    .build()
                billingClient.queryPurchasesAsync(subParams) { _, purchases ->
                    purchases.forEach { purchase ->
                        // 检查订阅是否处于活跃状态
                        val isActive = purchase.isAutoRenewing || purchase.purchaseState == Purchase.PurchaseState.PURCHASED
                        androidx.preference.PreferenceManager.getDefaultSharedPreferences(context)
                            .edit()
                            .putBoolean("Purchased_\(purchase.products.first())", isActive)
                            .apply()
                    }
                }
            }
        }

        override fun onBillingServiceDisconnected() {
            // 断开连接后尝试重连
            billingClient.startConnection(this)
        }
    })
}

private fun updatePurchasedStatus(purchases: List<Purchase>) {
    purchases.forEach { purchase ->
        androidx.preference.PreferenceManager.getDefaultSharedPreferences(purchases.first().purchaseToken)
            .edit()
            .putBoolean("Purchased_\(purchase.products.first())", true)
            .apply()
    }
}

2. 服务器端验证

同样,不要只依赖客户端结果,后端调用Google Play Developer API的purchases.products.get或purchases.subscriptions.get接口验证购买凭证,确保状态真实可靠。

通用注意事项

  • 进入内购页面时强制刷新状态:不要依赖上次的本地缓存,因为用户可能在其他设备购买、退款或变更订阅。
  • 监听退款和状态变更:iOS可以通过App Store服务器通知接收退款、订阅过期等事件;Android可以通过BillingClient的监听接口或Play Developer API的推送通知。
  • 本地缓存仅作优化:本地缓存是为了提升页面加载速度,但每次进入页面都要触发一次远程查询或凭证验证,避免状态不一致。

内容的提问来源于stack exchange,提问作者Kriti Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:08:01