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

StoreKit 2:无需额外文件,如何验证已激活自动续订订阅?

StoreKit 2迁移:无Products.plist配置Watch端订阅检查

问题背景

  • 已有在App Store Connect配置完成的自动续订订阅应用,正在从SwiftyStoreKit迁移至StoreKit 2
  • 不想创建单独的Products.plist文件,需配置WatchStoreManager类检查用户已激活的订阅
  • 当前代码抛出Error Domain=ASDErrorDomain Code=509 "No active account"错误,无法关联现有内购产品

解决方案

核心是直接通过StoreKit 2的API获取App Store Connect中已配置的产品,并验证用户订阅状态,无需依赖本地plist或storekit文件。以下是修改后的完整实现:

完整代码实现

import Foundation
import StoreKit

typealias Transaction = StoreKit.Transaction

public enum StoreError: Error {
    case failedVerification
    case productFetchFailed(Error)
    case noActiveSubscription
}

@available(watchOSApplicationExtension 8.0, *)
class WatchStoreManager: ObservableObject {
    var updateListenerTask: Task<Void, Error>? = nil
    // 替换为你在App Store Connect配置的实际订阅产品ID数组
    private let subscriptionProductIDs = ["com.yourdomain.your-subscription-id"]
    
    @Published var hasActiveSubscription = false
    
    init() {
        print("Init called in WatchStoreManager")
        updateListenerTask = listenForTransactions()
        // 初始化时立即检查订阅状态
        Task { await checkCurrentSubscriptionStatus() }
    }
    
    deinit {
        updateListenerTask?.cancel()
    }
    
    func listenForTransactions() -> Task<Void, Error> {
        return Task.detached {
            for await result in Transaction.updates {
                do {
                    let transaction = try self.checkVerified(result)
                    print("已验证交易")
                    await self.updateCustomerProductStatus()
                    await transaction.finish()
                } catch {
                    print("交易验证失败: \(error.localizedDescription)")
                }
            }
        }
    }
    
    func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
        switch result {
        case .unverified:
            throw StoreError.failedVerification
        case .verified(let safe):
            return safe
        }
    }
    
    // 直接从App Store获取订阅产品信息
    private func fetchSubscriptionProducts() async throws -> [Product] {
        do {
            let products = try await Product.products(for: subscriptionProductIDs)
            return products
        } catch {
            throw StoreError.productFetchFailed(error)
        }
    }
    
    // 检查当前用户的有效订阅状态
    func checkCurrentSubscriptionStatus() async {
        hasActiveSubscription = false
        do {
            // 获取用户当前所有未过期的订阅权益
            let entitlements = try await Transaction.currentEntitlements
            for await result in entitlements {
                guard let transaction = try? checkVerified(result) else { continue }
                
                // 验证产品ID匹配且订阅未过期
                if subscriptionProductIDs.contains(transaction.productID),
                   transaction.expirationDate?.compare(Date()) == .orderedDescending {
                    hasActiveSubscription = true
                    break
                }
            }
        } catch {
            print("检查订阅状态失败: \(error.localizedDescription)")
        }
    }
    
    // 订阅状态更新时同步刷新
    func updateCustomerProductStatus() async {
        await checkCurrentSubscriptionStatus()
    }
}

关键修改说明

  1. 移除本地产品文件依赖:直接通过Product.products(for:)方法传入App Store Connect配置的订阅ID数组,无需本地plist/storekit文件
  2. 订阅状态校验逻辑:
    • 用Transaction.currentEntitlements获取用户当前所有有效订阅权益
    • 校验交易产品ID是否匹配、订阅是否未过期,以此判断是否为激活状态
  3. 状态实时同步:通过@Published属性hasActiveSubscription对外暴露订阅状态,方便UI层监听更新
  4. 错误处理增强:新增产品获取失败、无有效订阅的错误类型,便于调试定位问题

509错误解决要点

  • 确保测试设备/模拟器已登录沙盒测试Apple ID(不要用真实ID,可在系统设置中切换)
  • 确认App Store Connect中订阅产品状态为「可供销售」,且Bundle ID与Watch App完全一致
  • 检查Watch App的签名配置,确保已开启内购权限
  • 测试时需连接网络,保证StoreKit能正常访问App Store沙盒服务器

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 09:00:51