黑苹果macOS StoreKit 2现代内购与订阅管理框架完全指南

发布时间:2026年6月 | 分类:黑苹果 | 关键词:StoreKit 2、内购开发、订阅管理

前言:StoreKit 2带来的内购革命

Apple在2022年随Xcode 14推出的StoreKit 2是对内购开发的一次全面革新。基于Swift Concurrency构建的全新API彻底取代了原有的delegate回调模式,让内购和订阅管理的代码变得更加简洁和可靠。对于在黑苹果上进行macOS应用开发的独立开发者来说,理解并掌握StoreKit 2是实现商业变现的关键能力。

本文将从StoreKit 2的核心概念出发,逐步讲解产品查询、购买流程、交易验证、订阅管理等完整链路,并特别关注在黑苹果环境下的开发调试要点。

一、StoreKit 2架构概览

StoreKit 2基于Swift 5.5的async/await语法构建,提供了更加类型安全和可组合的API设计。相比旧版StoreKit,主要变化包括:

1.1 核心类型对比

StoreKit 1StoreKit 2说明
SKProductProduct商品信息,新增类型属性
SKPaymentProduct.purchase()购买变为产品的方法
SKPaymentTransactionTransaction交易记录,自动验证
SKPaymentQueueTransaction.updates交易监听,AsyncSequence
SKProductsRequestProduct.products(for:)商品查询,async简化
ReceiptTransaction JWS收据验证变为JWS签名验证

1.2 新增关键特性

  • 自动JWS验证:Transaction自带Apple签名,无需服务端验证收据
  • Swift Concurrency原生支持:所有API均为async函数
  • Product type enum:类型安全的商品类型区分
  • Transaction.updates:AsyncSequence监听交易变更
  • AppTransaction:应用购买记录的统一查询
  • RefundChecker:退款状态实时监听

二、商品查询与展示

在实现内购之前,首先需要从App Store Connect查询已配置的商品信息。

2.1 查询商品列表

import StoreKit

// 查询指定商品ID的产品信息
let productIDs = ["com.example.pro_monthly", "com.example.pro_yearly", "com.example.coin_pack"]
let products = try await Product.products(for: productIDs)

for product in products {
    print("商品名称: \(product.displayName)")
    print("商品描述: \(product.description)")
    print("价格: \(product.displayPrice)")
    print("类型: \(product.type)")
    
    // 订阅特有属性
    if case .autoRenewable = product.type {
        if let subscription = product.subscription {
            print("订阅周期: \(subscription.subscriptionGroupID)")
            print("优惠价格: \(subscription.introductoryOffer?.price ?? 0)")
        }
    }
}

2.2 Product类型详解

StoreKit 2中的Product通过type属性区分不同商品类型:

switch product.type {
case .autoRenewable:
    // 自动续期订阅:月度/年度会员
    break
case .nonRenewable:
    // 非续期订阅:限时服务
    break
case .consumable:
    // 消耗型:金币、道具
    break
case .nonConsumable:
    // 非消耗型:永久解锁功能
    break
@unknown default:
    break
}

2.3 订阅优惠信息

对于自动续期订阅,可以获取丰富的优惠信息:

if let subscription = product.subscription {
    // 新用户优惠
    if let intro = subscription.introductoryOffer {
        print("优惠类型: \(intro.paymentMode)")
        print("优惠价格: \(intro.displayPrice)")
        print("优惠周期: \(intro.period.value) \(intro.period.unit)")
    }
    
    // 促销优惠(需服务端签名)
    for promo in subscription.promotionalOffers {
        print("促销ID: \(promo.id)")
        print("促销价格: \(promo.displayPrice)")
    }
}

三、购买流程实现

StoreKit 2将购买流程简化为一个async函数调用,大大降低了实现复杂度。

3.1 基础购买流程

import StoreKit

func purchase(_ product: Product) async throws -> Transaction? {
    let result = try await product.purchase()
    
    switch result {
    case .success(let verification):
        // 验证交易签名
        let transaction = try checkVerified(verification)
        print("购买成功: \(transaction.productID)")
        
        // 完成交易(必须调用,否则交易会一直待处理)
        await transaction.finish()
        return transaction
        
    case .userCancelled:
        print("用户取消购买")
        return nil
        
    case .pending:
        // 交易需要家长批准(Ask to Buy)
        print("交易待处理")
        return nil
        
    @unknown default:
        return nil
    }
}

// 验证交易JWS签名
func checkVerified(_ result: VerificationResult<Transaction>) throws -> Transaction {
    switch result {
    case .unverified(let transaction, let error):
        throw error
    case .verified(let transaction):
        return transaction
    }
}

3.2 交易监听与处理

对于可能发生在App外部的交易(如家庭共享、优惠兑换),需要持续监听交易更新:

import StoreKit

class TransactionObserver {
    var updateTask: Task<Void, Never>?
    
    func startObserving() {
        updateTask = Task(priority: .background) {
            for await result in Transaction.updates {
                do {
                    let transaction = try checkVerified(result)
                    // 处理交易:解锁功能、发放虚拟货币等
                    await handleTransaction(transaction)
                    await transaction.finish()
                } catch {
                    print("交易验证失败: \(error)")
                }
            }
        }
    }
    
    func stopObserving() {
        updateTask?.cancel()
    }
}

四、订阅管理深度实现

自动续期订阅是许多macOS应用的核心商业模式,StoreKit 2提供了强大的订阅管理能力。

4.1 当前订阅状态查询

import StoreKit

func getCurrentSubscriptionStatus(groupID: String) async throws -> Product.SubscriptionInfo.Status? {
    let statuses = try await Product.SubscriptionInfo.status(for: groupID)
    
    // 找到有效的订阅状态
    let validStatus = statuses
        .filter { $0.state == .subscribed || $0.state == .inGracePeriod || $0.state == .inBillingRetryPeriod }
        .first
    
    return validStatus
}

// 获取最高等级的订阅
func getHighestSubscription() async throws -> Transaction? {
    let groupID = "your_subscription_group_id"
    let statuses = try await Product.SubscriptionInfo.status(for: groupID)
    
    var highestTransaction: Transaction?
    var highestLevel = 0
    
    for status in statuses {
        if case .verified(let transaction) = status.transaction {
            // 假设产品ID包含等级信息
            let level = getSubscriptionLevel(transaction.productID)
            if level > highestLevel {
                highestLevel = level
                highestTransaction = transaction
            }
        }
    }
    return highestTransaction
}

4.2 订阅状态变化处理

func handleSubscriptionStatusChange() async {
    let groupID = "your_subscription_group_id"
    
    for await result in Transaction.updates {
        guard let transaction = try? checkVerified(result) else { continue }
        
        if transaction.productType == .autoRenewable {
            // 获取最新订阅状态
            let statuses = try? await Product.SubscriptionInfo.status(for: groupID)
            let currentStatus = statuses?.first { $0.state == .subscribed }
            
            switch currentStatus?.state {
            case .subscribed:
                print("用户已订阅")
            case .expired:
                print("订阅已过期")
            case .inGracePeriod:
                print("订阅宽限期")
            case .inBillingRetryPeriod:
                print("账单重试期")
            case .revoked:
                print("订阅被撤销")
            default:
                break
            }
        }
    }
}

4.3 订阅优惠签名

对于促销优惠,需要在服务端生成签名。以下是签名生成逻辑:

import CryptoKit

func generatePromotionalOfferSignature(
    keyID: String,
    nonce: UUID,
    timestamp: Int64,
    productID: String,
    offerID: String,
    applicationUsername: String?,
    privateKey: P256.KeyAgreement.PrivateKey
) throws -> String {
    let payload = "\(keyID)\(nonce.uuidString)\(timestamp)\(productID)\(offerID)\(applicationUsername ?? "")"
    let signature = try privateKey.signature(for: Data(payload.utf8))
    return signature.rawRepresentation.base64EncodedString()
}

五、黑苹果StoreKit 2开发注意事项

5.1 StoreKit Testing in Xcode

在黑苹果上开发StoreKit 2最大的优势是可以使用Xcode内置的StoreKit Testing环境,无需真机即可完整测试内购流程:

  • 创建StoreKit Configuration文件,定义产品和订阅
  • 在Scheme中启用StoreKit Configuration
  • 使用Xcode的StoreKit管理器控制交易状态
  • 模拟各种购买场景:购买、退款、订阅续期等

5.2 黑苹果环境特殊考量

项目说明建议
沙盒测试StoreKit Configuration可完全替代使用Xcode内置测试
收据验证StoreKit 2无需传统收据使用JWS验证
网络连接需稳定的网络连接App Store确保博通网卡正常工作
Xcode版本需Xcode 14+黑苹果Sonoma上可安装最新版

5.3 调试技巧

  1. 使用Transaction.currentEntitlements快速检查用户的当前权益
  2. 在StoreKit Configuration中启用"Enable StoreKit Logging"查看详细日志
  3. 使用断点在purchase()调用处暂停,观察完整的交易流程
  4. 测试退款场景时,使用Xcode的"Refund Purchase"功能

六、App Store Server API集成

对于需要服务端验证的场景,App Store Server API V2提供了完整的交易管理能力。

6.1 JWT签名认证

import CryptoKit
import Foundation

func generateJWT(keyID: String, issuerID: String, bundleID: String, privateKey: P256.KeyAgreement.PrivateKey) throws -> String {
    let header = "{\"alg\":\"ES256\",\"kid\":\"\(keyID)\",\"typ\":\"JWT\"}"
    let now = Int(Date().timeIntervalSince1970)
    let payload = "{\"iss\":\"\(issuerID)\",\"iat\":\(now),\"exp\":\(now + 3600),\"aud\":\"appstoreconnect-v1\",\"bid\":\"\(bundleID)\"}"
    
    // Base64URL编码
    let headerData = Data(header.utf8).base64URLEncodedString()
    let payloadData = Data(payload.utf8).base64URLEncodedString()
    let signingInput = "\(headerData).\(payloadData)"
    
    // ES256签名
    let signature = try privateKey.signature(for: Data(signingInput.utf8))
    let signatureData = signature.rawRepresentation.base64URLEncodedString()
    
    return "\(signingInput).\(signatureData)"
}

6.2 服务器通知V2

App Store Server Notifications V2通过Webhook实时推送交易状态变更:

// 通知类型示例
// DID_RENEW: 订阅成功续期
// DID_FAIL_TO_RENEW: 续期失败
// REFUND: 用户获得退款
// REVOKE: 撤销家庭共享访问
// CONSUMPTION_REQUEST: 消耗型购买请求

总结

StoreKit 2代表了Apple内购开发的未来方向。基于Swift Concurrency的API设计让内购代码从繁琐的回调嵌套变为清晰的线性流程,JWS自动验证消除了收据验证的痛点。对于在黑苹果上进行macOS应用开发的独立开发者来说,StoreKit 2 + Xcode StoreKit Testing的组合提供了完整的开发和测试环境。

关键要点

  • StoreKit 2基于async/await,代码更简洁可靠
  • Transaction的JWS自动验证消除了收据验证的复杂性
  • Transaction.updates AsyncSequence替代了delegate回调
  • Product.SubscriptionInfo提供完整的订阅状态管理
  • 黑苹果上使用StoreKit Configuration可以完整测试所有购买场景

如果你在黑苹果上进行StoreKit 2开发时遇到任何问题,欢迎在评论区留言讨论!🍎

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。