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

StoreKit2无法检测订阅取消更新问题排查及替代方案咨询

问题分析与解决方案

一、代码中遗漏的关键逻辑

你无法接收应用外订阅取消通知的核心原因是:StoreKit的Transaction.updates序列仅在发生新交易(如购买、续订、升级/降级、退款)时触发,用户在设置中取消自动续订不会产生新交易,因此不会触发updates事件。你的代码存在以下几个问题:

  • 缺少主动重新校验权限的触发时机:应用外取消自动续订或通过Xcode工具模拟取消后,没有触发updatePurchasedProducts()的逻辑,仅在应用启动或完成购买时才会检查权限。
  • updatePurchasedProducts逻辑不完整:未处理cancellationDate字段(用户取消自动续订时会设置该字段,而非revocationDate——后者对应退款场景);同时错误地调用了transaction.finish(),currentEntitlements返回的是当前有效订阅凭证,无需手动finish,否则会清除本地缓存导致后续校验异常。
  • observeTransactionUpdates存在重复操作:收到更新事件后调用updatePurchasedProducts(),该方法已处理所有订阅状态,后续再次调用transaction.finish()属于重复操作,可能引发状态混乱。

二、代码修复建议

1. 修正updatePurchasedProducts逻辑

补充cancellationDate判断,移除不必要的finish()调用:

func updatePurchasedProducts() async {
    // 先重置权限,避免多订阅状态冲突
    subscriptionManager.unlimitedWallets = false
    subscriptionManager.unlimitedReceiptScans = false
    
    for await result in SkTransaction.currentEntitlements {
        guard case .verified(let transaction) = result else {
            continue
        }

        let currentDate = Date()
        let isSubscriptionActive = transaction.expirationDate == nil || currentDate <= transaction.expirationDate!
        let isRevoked = transaction.revocationDate != nil

        if !isRevoked && isSubscriptionActive {
            switch transaction.productID {
            case "plus":
                print("Plus subscription active")
                subscriptionManager.unlimitedWallets = true
            case "premium":
                print("Premium subscription active")
                subscriptionManager.unlimitedWallets = true
                subscriptionManager.unlimitedReceiptScans = true
            default:
                print("Unknown product ID \(transaction.productID)")
            }
        }
        // 不要finish currentEntitlements中的事务
    }
}

2. 增加主动校验时机

在以下场景主动调用updatePurchasedProducts():

  • 应用从后台回到前台时
  • 用户进入订阅管理页面时
  • 应用启动时(每日一次即可)

示例:在SwiftUI中监听应用前台通知:

// 在ContentView中添加
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
    Task {
        await purchaseManager.updatePurchasedProducts()
    }
}

3. 修正observeTransactionUpdates逻辑

仅处理新交易,finish后再更新权限:

private func observeTransactionUpdates() -> Task<Void, Never> {
    Task(priority: .background) { [unowned self] in
        for await result in SkTransaction.updates {
            print("transaction updated observed")
            switch result {
            case .verified(let transaction):
                await transaction.finish()
                await updatePurchasedProducts()
            case .unverified(let transaction, let error):
                print("Unverified transaction: \(transaction), error: \(error)")
            }
        }
    }
}

三、替代方案可行性分析

你考虑的存储订阅过期日期并对比当前日期判断权限的方案存在明显局限性:

  • 无法处理退款场景:用户申请退款后订阅立即失效,但过期日期未到,会误判权限仍有效。
  • 无法感知自动续订取消状态:用户取消自动续订后,当前周期内订阅仍有效,但无法向用户提示后续不会续订的状态。
  • 系统时间篡改风险:用户修改设备时间会导致日期对比失效,权限判断错误。

因此该方案仅能作为临时补充,不能替代StoreKit的currentEntitlements实时校验逻辑,建议优先采用主动触发updatePurchasedProducts()的方案。

测试说明

Xcode的Manage StoreKit Transactions工具模拟取消订阅后,需要重启应用或主动触发权限校验才能看到状态更新,这是工具的正常行为,不属于bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 09:26:02