License Platform Language Build Issues Slack

SwiftyStoreKit 是一个轻量级的应用内购买框架,适用于 iOS、tvOS、watchOS、macOS 和 Mac Catalyst。

特性

SwiftyStoreKit 的替代方案

在 WWDC21 期间,Apple 推出了 StoreKit 2,这是一个全新的 Swift API,用于应用内购买和自动续订订阅。

虽然在这个项目中支持 StoreKit 2 非常理想,但过去一年 进展甚微,并且大多数问题仍然没有得到解答

幸运的是,有一些非常好的 SwiftyStoreKit 替代方案,由真正的公司支持。通过选择他们的产品,您将做出安全的选择并获得更好的支持。

RevenueCat

RevenueCat 是 SwiftyStoreKit 的一个很好的替代方案,以非常合理的价格提供出色的 API、支持和更多功能。

如果您一直在使用 SwiftyStoreKit 并且想要迁移到 RevenueCat,本指南涵盖了您需要的一切

或者,如果您刚开始,请考虑完全跳过 SwiftyStoreKit 并注册 RevenueCat

Glassfy

Glassfy 使构建、处理和优化应用内订阅变得容易。如果您从 SwiftyStoreKit 切换到 Glassfy,您将通过使用此 联盟链接 获得 20% 的折扣。

作者注:如果您使用上面的链接注册,我将从 Glassfy 获得联盟佣金,您无需支付任何费用。我只会推荐我个人了解并相信会对您有所帮助的产品。

Apphud

Apphud 不仅仅是进行购买和验证收据。 Apphud 是您应用增长的一体化基础设施。 免费注册并试用。

或者您可以了解 如何将您的应用程序从 SwiftyStoreKit 迁移到 Apphud

Adapty

借助 Adapty,您可以按照这些 简单的步骤 在短短一小时内设置订阅,并立即使用 Paywall Builder 启动应用内购买。 Adapty 不仅为您提供了嵌入购买的工具,还有助于您的客户成长。 最好的部分是,对于收入低于 1 万美元的应用程序,它是免费的

欢迎贡献

SwiftyStoreKit 使数量惊人的开发人员能够轻松地集成应用内购买。 然而,该项目现在是社区主导的。 我们需要帮助来构建功能和编写测试(参见 issue #550)。

需要维护者

作者不再积极维护该项目。 如果您想成为维护者,请加入 Slack 工作区并进入#maintainers频道。 展望未来,SwiftyStoreKit 应该由社区为社区制作。

更多信息请点击:SwiftyStoreKit 的未来:需要维护者

要求

如果您在过去五年内发布过应用程序,那么您可能可以使用它。 某些功能(例如折扣)仅在新操作系统版本上可用,但大多数功能都可用于

iOS watchOS tvOS macOS Mac Catalyst
8.0 6.2 9.0 10.10 13.0

安装

有很多方法可以为您的项目安装 SwiftyStoreKit。 Swift Package Manager、CocoaPods 和 Carthage 集成是首选和推荐的方法。

无论如何,请务必在您可能使用它的任何地方导入该项目

import SwiftyStoreKit

Swift Package Manager

Swift Package Manager 是一种用于自动化 Swift 代码分发的工具,已集成到 Xcode 和 Swift 编译器中。 这是推荐的安装方法。 SwiftyStoreKit 的更新将始终立即提供给使用 SPM 的项目。 SPM 也直接与 Xcode 集成。

如果您使用的是 Xcode 11 或更高版本

  1. 点击 File
  2. Swift Packages
  3. Add Package Dependency...
  4. 指定 SwiftyStoreKit 的 git URL。
https://github.com/bizz84/SwiftyStoreKit.git

Carthage

要使用 Carthage 将 SwiftyStoreKit 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它

github "bizz84/SwiftyStoreKit"

注意:请确保您安装了最新的 Carthage。

CocoaPods

SwiftyStoreKit 可以作为 CocoaPod 安装,并构建为 Swift 框架。 要安装,请将其包含在您的 Podfile 中。

use_frameworks!

pod 'SwiftyStoreKit'

贡献

有疑问/想要提交 pull requests / 想要贡献?点击这里阅读

文档

完整的文档可在SwiftyStoreKit Wiki上找到。 随着 SwiftyStoreKit(以及 Apple 的 StoreKit)获得功能、平台和实现方法,新信息将添加到 Wiki 中。 基本文档可在此自述文件中找到,应该足以让您启动并运行。

应用启动

完成事务

Apple 建议在应用启动后立即注册事务观察者

在启动时添加您的应用程序的观察者可确保它在您应用程序的所有启动期间持续存在,从而允许您的应用程序接收所有付款队列通知。

SwiftyStoreKit 通过在应用程序启动时调用 completeTransactions() 来支持这一点

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
	// see notes below for the meaning of Atomic / Non-Atomic
	SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
	    for purchase in purchases {
	        switch purchase.transaction.transactionState {
	        case .purchased, .restored:
	            if purchase.needsFinishTransaction {
	                // Deliver content from server, then:
	                SwiftyStoreKit.finishTransaction(purchase.transaction)
	            }
	            // Unlock content
	        case .failed, .purchasing, .deferred:
	            break // do nothing
	        }
	    }
	}
    return true
}

如果在此时有任何挂起的事务,这些事务将由完成块报告,以便可以更新应用程序状态和 UI。

如果没有挂起的事务,则不会调用完成块。

请注意,completeTransactions() 只能在您的代码中调用一次,即在 application(:didFinishLaunchingWithOptions:) 中。

购买

检索产品信息

SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
    if let product = result.retrievedProducts.first {
        let priceString = product.localizedPrice!
        print("Product: \(product.localizedDescription), price: \(priceString)")
    }
    else if let invalidProductId = result.invalidProductIDs.first {
        print("Invalid product identifier: \(invalidProductId)")
    }
    else {
        print("Error: \(result.error)")
    }
}

购买产品(给定产品 ID)

SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: true) { result in
    switch result {
    case .success(let purchase):
        print("Purchase Success: \(purchase.productId)")
    case .error(let error):
        switch error.code {
        case .unknown: print("Unknown error. Please contact support")
        case .clientInvalid: print("Not allowed to make the payment")
        case .paymentCancelled: break
        case .paymentInvalid: print("The purchase identifier was invalid")
        case .paymentNotAllowed: print("The device is not allowed to make the payment")
        case .storeProductNotAvailable: print("The product is not available in the current storefront")
        case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
        case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
        case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
        default: print((error as NSError).localizedDescription)
        }
    }
}
SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
    switch result {
    case .success(let product):
        // fetch content from your server, then:
        if product.needsFinishTransaction {
            SwiftyStoreKit.finishTransaction(product.transaction)
        }
        print("Purchase Success: \(product.productId)")
    case .error(let error):
        switch error.code {
        case .unknown: print("Unknown error. Please contact support")
        case .clientInvalid: print("Not allowed to make the payment")
        case .paymentCancelled: break
        case .paymentInvalid: print("The purchase identifier was invalid")
        case .paymentNotAllowed: print("The device is not allowed to make the payment")
        case .storeProductNotAvailable: print("The product is not available in the current storefront")
        case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
        case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
        case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
        default: print((error as NSError).localizedDescription)
        }
    }
}

其他购买文档

这些其他主题可在 Wiki 上找到

恢复以前的购买

根据 Apple - 恢复购买的产品

在大多数情况下,您的应用程序需要做的就是刷新其收据并交付其收据中的产品。 刷新的收据包含用户在此应用程序中、在此设备或任何其他设备上购买的记录。

恢复已完成的事务会为用户进行的每个已完成的事务创建一个新事务,本质上是为您的事务队列观察者重播历史记录。

请参阅下面的收据验证部分,了解如何使用收据恢复以前的购买。

本节展示了如何使用 restorePurchases 方法恢复已完成的事务。 成功后,该方法将返回所有非消耗型购买以及所有自动续订订阅购买,无论它们是否已过期

SwiftyStoreKit.restorePurchases(atomically: true) { results in
    if results.restoreFailedPurchases.count > 0 {
        print("Restore Failed: \(results.restoreFailedPurchases)")
    }
    else if results.restoredPurchases.count > 0 {
        print("Restore Success: \(results.restoredPurchases)")
    }
    else {
        print("Nothing to Restore")
    }
}
SwiftyStoreKit.restorePurchases(atomically: false) { results in
    if results.restoreFailedPurchases.count > 0 {
        print("Restore Failed: \(results.restoreFailedPurchases)")
    }
    else if results.restoredPurchases.count > 0 {
        for purchase in results.restoredPurchases {
            // fetch content from your server, then:
            if purchase.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(purchase.transaction)
            }
        }
        print("Restore Success: \(results.restoredPurchases)")
    }
    else {
        print("Nothing to Restore")
    }
}

原子性/非原子性是什么意思?

有关原子性与非原子性恢复的更多信息,请在此处查看 Wiki 页面

下载 Apple 托管的内容

有关下载托管内容的更多信息,请访问 Wiki

要启动下载(这可以在 purchaseProduct()completeTransactions()restorePurchases() 中完成)

SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quantity: 1, atomically: false) { result in
    switch result {
    case .success(let product):
        let downloads = purchase.transaction.downloads
        if !downloads.isEmpty {
            SwiftyStoreKit.start(downloads)
        }
    case .error(let error):
        print("\(error)")
    }
}

要检查更新的下载,请在您的 AppDelegate 中设置一个 updatedDownloadsHandler

SwiftyStoreKit.updatedDownloadsHandler = { downloads in
    // contentURL is not nil if downloadState == .finished
    let contentURLs = downloads.flatMap { $0.contentURL }
    if contentURLs.count == downloads.count {
        // process all downloaded files, then finish the transaction
        SwiftyStoreKit.finishTransaction(downloads[0].transaction)
    }
}

要控制下载的状态,SwiftyStoreKit 提供 start()pause()resume()cancel() 方法。

收据验证

此助手可用于检索(加密的)本地收据数据

let receiptData = SwiftyStoreKit.localReceiptData
let receiptString = receiptData.base64EncodedString(options: [])
// do your receipt validation here

但是,收据文件可能丢失或已过期。 使用此方法获取更新的收据

SwiftyStoreKit.fetchReceipt(forceRefresh: true) { result in
    switch result {
    case .success(let receiptData):
        let encryptedReceipt = receiptData.base64EncodedString(options: [])
        print("Fetch receipt success:\n\(encryptedReceipt)")
    case .error(let error):
        print("Fetch receipt failed: \(error)")
    }
}

使用此方法(可选)刷新收据并一步执行验证。

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: false) { result in
    switch result {
    case .success(let receipt):
        print("Verify receipt success: \(receipt)")
    case .error(let error):
        print("Verify receipt failed: \(error)")
    }
}

有关收据验证的更多详细信息,请访问 wiki

验证购买和订阅

使用 verifyReceipt 方法检索收据后,您可以按产品标识符验证您的购买和订阅。

验证购买

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
    case .success(let receipt):
        let productId = "com.musevisions.SwiftyStoreKit.Purchase1"
        // Verify the purchase of Consumable or NonConsumable
        let purchaseResult = SwiftyStoreKit.verifyPurchase(
            productId: productId,
            inReceipt: receipt)
            
        switch purchaseResult {
        case .purchased(let receiptItem):
            print("\(productId) is purchased: \(receiptItem)")
        case .notPurchased:
            print("The user has never purchased \(productId)")
        }
    case .error(let error):
        print("Receipt verification failed: \(error)")
    }
}

验证订阅

该功能可用于检查订阅是否曾被购买,以及它是否仍然有效或已过期。

摘自Apple - Working with Subscriptions(Apple - 使用订阅)

记录每个内容发布的日期。从每个收据条目读取“原始购买日期”和“订阅到期日期”字段,以确定订阅的开始和结束日期。

如果找到给定产品 ID 的一个或多个订阅,它们将作为 ReceiptItem 数组返回,并按 expiryDate 排序,第一个是最新的。

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
    case .success(let receipt):
        let productId = "com.musevisions.SwiftyStoreKit.Subscription"
        // Verify the purchase of a Subscription
        let purchaseResult = SwiftyStoreKit.verifySubscription(
            ofType: .autoRenewable, // or .nonRenewing (see below)
            productId: productId,
            inReceipt: receipt)
            
        switch purchaseResult {
        case .purchased(let expiryDate, let items):
            print("\(productId) is valid until \(expiryDate)\n\(items)\n")
        case .expired(let expiryDate, let items):
            print("\(productId) is expired since \(expiryDate)\n\(items)\n")
        case .notPurchased:
            print("The user has never purchased \(productId)")
        }

    case .error(let error):
        print("Receipt verification failed: \(error)")
    }
}

有关验证订阅的更多文档,请访问 wiki

订阅组

摘自Apple Docs - Offering Subscriptions(Apple 文档 - 提供订阅)

订阅组是一组您可以创建的 App 内购买项目,用于向用户提供一系列内容产品、服务级别或期限,以最好地满足他们的需求。 用户一次只能购买一个订阅组中的一个订阅。 如果用户想购买多种类型的订阅——例如,订阅流媒体应用中的多个频道——您可以将这些 App 内购买项目放在不同的订阅组中。

您可以使用 verifySubscriptions 方法验证同一组内的所有订阅。在 wiki 上了解更多信息

注意

该框架在现有的 StoreKit 框架之上提供了一个基于简单块的 API,具有强大的错误处理能力。它 会在本地持久化应用内购买数据。这取决于客户端使用他们选择的存储解决方案(即 NSUserDefaults、CoreData、Keychain)来实现。

更新日志

请参阅发布页面

示例代码

该项目包括iOSmacOS 的演示应用程序,展示了如何使用 SwiftyStoreKit。请注意,演示应用程序中预先注册的 App 内购买项目仅用于说明目的,可能无法正常工作,因为 iTunes Connect 可能会使它们失效。

重要阅读

我还写了一篇关于在 Medium 上构建 SwiftyStoreKit 的文章

故障排除

视频教程

Jared Davidson: In App Purchases! (Swift 3 in Xcode : Swifty Store Kit) (App 内购买!(Xcode 中的 Swift 3 : Swifty Store Kit))

@rebeloper: Ultimate In-app Purchases Guide (终极 App 内购买指南)

书面教程

致谢

非常感谢 phimage 添加 macOS 支持和收据验证。

使用 SwiftyStoreKit 的应用程序

很高兴在这里展示使用 SwiftyStoreKit 的应用程序。 欢迎提交 Pull Request :)

完整的应用程序列表已发布在 AppSight 上。