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

如何为iOS应用的两个订阅产品配置互斥免费试用期

实现互斥试用期的方案(StoreKit2)

核心逻辑

通过查询用户的历史交易记录,判断是否已经使用过任意目标产品的试用期,以此限制另一个产品的试用入口,同时在购买前做二次校验防止绕过。


1. 查询用户是否使用过任意试用期

利用StoreKit2的Transaction.all()获取用户所有交易记录,筛选出类型为试用且属于目标产品的有效记录:

import StoreKit

func hasUsedAnyTrial() async -> Bool {
    let targetProductIDs = ["com.myProduct.monthly", "com.myProduct.yearly"]
    do {
        let allTransactions = try await Transaction.all()
        // 筛选未被撤销的试用交易
        let validTrials = allTransactions.filter {
            guard let productID = $0.productID, targetProductIDs.contains(productID) else { return false }
            return $0.type == .trial && !$0.isRevoked
        }
        return !validTrials.isEmpty
    } catch {
        print("获取交易记录失败:\(error.localizedDescription)")
        return false
    }
}

2. 动态调整产品展示UI

根据查询结果,隐藏已使用过试用期产品的试用入口,只显示直接购买选项:

// 提前定义目标产品ID数组
let targetProductIDs = ["com.myProduct.monthly", "com.myProduct.yearly"]

func configureProductUI(for product: Product) async {
    let hasUsedTrial = await hasUsedAnyTrial()
    guard targetProductIDs.contains(product.id) else { return }
    
    if hasUsedTrial {
        // 隐藏试用标签,显示原价
        trialBadge.isHidden = true
        priceLabel.text = product.displayPrice
        buyButton.setTitle("立即购买", for: .normal)
    } else {
        // 展示试用信息
        trialBadge.text = "免费试用1个月"
        priceLabel.text = "试用结束后¥\(product.price)"
        buyButton.setTitle("开始试用", for: .normal)
    }
}

3. 购买前的二次校验

为避免用户通过篡改UI绕过限制,发起购买请求前再次验证试用状态:

func initiatePurchase(for product: Product) async {
    let hasUsedTrial = await hasUsedAnyTrial()
    guard targetProductIDs.contains(product.id) else {
        // 处理非目标产品购买逻辑
        return
    }
    
    if hasUsedTrial {
        let alert = UIAlertController(title: "提示", message: "您已享受过免费试用期,无法重复使用", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "确定", style: .default))
        present(alert, animated: true)
        return
    }
    
    // 正常发起购买流程
    do {
        let purchaseResult = try await product.purchase()
        switch purchaseResult {
        case .success(let verification):
            let transaction = try verification.payloadValue
            // 处理交易完成逻辑(比如解锁权限)
            await transaction.finish()
        case .userCancelled:
            break
        case .pending:
            // 处理待确认状态(如家庭共享审批)
            break
        @unknown default:
            break
        }
    } catch {
        print("购买失败:\(error.localizedDescription)")
    }
}

4. 特殊场景处理

  • 退款后重置试用:如果业务允许用户退款后再次试用,可以移除!$0.isRevoked的判断,或者单独处理已撤销的交易记录。
  • 跨设备同步:若需要多设备同步试用状态,建议将用户的试用记录同步到自有服务器,避免仅依赖本地StoreKit查询可能出现的延迟或不一致。

内容的提问来源于stack exchange,提问作者Irfan Saeed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:52:50