SwiftyStoreKit 0.16.1

SwiftyStoreKit 0.16.1

测试已测试
语言语言 SwiftSwift
许可 MIT
发布最新版本2020 年 7 月
SPM支持 SPM

Andrea BizzottoSam Spencer 维护。




  • 作者
  • Andrea Bizzotto

License Platform Language Build Issues Slack

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

期望贡献

SwiftyStoreKit 使大量开发者能够无缝集成内购。然而,该项目现在已经由社区领导。我们需要帮助构建功能和编写测试(参见 问题 #550)。

需要维护者

  • 作者不再积极维护此项目。如果您想成为维护者,请加入 Slack 工作空间 并进入 #maintainers 频道。
  • 接下来,SwiftyStoreKit 应该由社区制造,由社区制造。

更多信息请参阅此处

加入 Slack

SwiftyStoreKit 在 Slack 上。 加入此处

内容

需求

如果你在过去五年内发布过应用,你很可能已经准备好了。一些功能(如折扣)仅在新 OS 版本上可用,但大多数功能可回溯至以下操作系统:

iOS watchOS tvOS macOS Mac Catalyst
8.0 6.2 9.0 10.10 13.0

安装

有几种方法可以安装 SwiftyStoreKit 到你的项目中。Swift 包管理器和 Carthage 集成是首选和推荐的方法。遗憾的是,CocoaPods 目前不受支持/已过时(详情请见下方)。

无论如何,确保在你可能使用 SwiftyStoreKit 的任何地方导入项目

import SwiftyStoreKit

Swift 包管理器

Swift 包管理器是一种用于自动化 Swift 代码分发的工具,它集成了 Xcode 和 Swift 编译器。这是推荐的安装方法。SwiftyStoreKit 的更新总是会立即对使用 SPM 的项目可用。SPM 也与 Xcode 直接集成。

如果你正在使用 Xcode 11 或更高版本

  1. 点击 文件
  2. Swift 包
  3. 添加包依赖...
  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'

功能

  • 超级容易使用的基于块的 API
  • 支持消耗性和非消耗性内购
  • 支持免费、自动续订和非续订订阅
  • 支持在 App Store 中开始的内购(iOS 11)
  • 支持订阅折扣和优惠
  • 远程收据验证
  • 验证购买、订阅、订阅组
  • 下载苹果托管的内容
  • 兼容 iOS、tvOS、watchOS、macOS 和 Catalyst

贡献

遇到问题/拉取请求/想要贡献?请在此阅读

应用启动

完成交易

苹果公司建议在应用启动时注册一个交易观察者作为推荐的操作

在启动时将应用观察者添加到其中可以确保在应用所有启动期间持续存在,从而允许应用接收所有支付队列通知。

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
}

如果在此时有挂起的交易,这些交易将通过完成块报告,以便应用状态和用户界面可以更新。

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

请注意,在代码中 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)
        }
    }
}

按SKProduct购买产品

这是上述方法的变体,当使用 retrieveProductsInfo 获取相应的 SKProduct 后可以用来购买产品

SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
    if let product = result.retrievedProducts.first {
        SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
            // handle result (same as above)
        }
    }
}

使用此 purchaseProduct 方法可以确保对 StoreKit 只进行一次网络调用以执行购买,而不是分别进行一次获取产品和一次执行购买的调用。

处理 App Store 上开始的购买(iOS 11)

iOS 11 在 SKPaymentTransactionObserver 上添加了一个新委托方法

@available(iOS 11.0, *)
optional public func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool

来自 Apple 文档

当用户在 App Store 中开始内购并继续在您的应用程序中执行事务时,将调用此委托方法。具体来说,如果您的应用程序已经安装,则此方法会自动调用。如果用户在 App Store 中开始内购时应用程序尚未安装,则用户在应用程序安装完成时收到通知。当用户点击通知时调用此方法。否则,如果用户手动打开应用程序,则仅在购买后不久打开应用程序时调用此方法。

SwiftyStoreKit 使用一个名为的操作符支持此功能,例如

SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
    // return true if the content can be delivered by your app
    // return false otherwise
}

为了在沙盒模式下测试此功能,请使用 Safari 打开此网址

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

有关 WWDC17 会议“StoreKit 新特性”幻灯片编号 165 显示上述链接)的更多信息。

恢复以前的购买

根据 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")
    }
}

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

您购买产品时会发生以下情况

  • 支付被添加到您的IAP支付队列。
  • 当支付被苹果处理完成后,支付队列将被更新,以便处理相应的交易。
  • 如果交易状态是购买恢复,则应用可以解锁用户购买的付费功能。
  • 应用应调用finishTransaction(_:)以完成购买。

这是苹果推荐的做法

您的应用应在成功处理交易并解锁用户购买的付费功能后,才调用finishTransaction(_:)

  • 购买在应用立即解锁用户购买的付费功能并调用finishTransaction(_:)时是原子的。如果您正在解锁应用内已有的功能,这是所需的。

  • 在您需要请求自己的服务器以解锁功能的情况下,可以使用非原子购买。

  • 注意:SwiftyStoreKit尚不支持非消耗性产品的苹果托管内容下载。请参阅此功能请求

SwiftyStoreKit提供了三种操作,可以是原子非原子的。

  • 进行购买
  • 恢复购买
  • 在应用启动时完成交易

通过苹果托管内容下载

引用苹果文档

在iTunes Connect中创建产品时,可以将一个或多个可下载内容与其关联。在运行时,当用户购买产品时,您的应用使用SKDownload对象从App Store下载内容。

您的应用永远不会直接创建一个SKDownload对象。相反,在支付处理完成后,您的应用读取事务对象的下载属性,以检索与事务关联的SKDownload对象数组。

要下载内容,您将在支付队列上排队一个下载对象并等待内容下载。下载完成后,读取下载对象的contentURL属性以获取下载内容的URL。在完成交易之前,您的应用必须处理下载的文件。例如,它可能将文件复制到包含持久内容的目录中。当所有下载完成时,您完成交易。交易完成后,下载对象不能排队到支付队列,任何指向下载内容的URL都无效。

要开始下载(这可以在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()方法。

收据验证

根据 Apple - 交付产品

应用收据包含了用户购买记录,并由Apple进行加密签名。更多信息,请参阅收据验证编程指南

当消耗性产品支付时,其信息会被添加到收据中,并在完成交易前一直保留。完成交易后,此信息将在下一次收据更新时(例如,用户进行下一次购买时)被删除。

所有其他类型购买的信息在支付后会添加到收据中,并且无限期保留在收据中。

当应用首次安装时,应用收据缺失。

一旦用户完成购买或恢复购买,StoreKit会将收据以文件形式创建并本地存储,文件位于Bundle.main.appStoreReceiptURL

检索本地收据(加密)

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

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)")
    }
}

此方法的工作原理如下

  • 如果forceRefresh = false,它将返回本地收据(如果文件缺失则刷新)。
  • 如果forceRefresh = true,无论何种情况都会刷新收据。

备注

  • 如果本地收据缺失或在调用fetchReceiptforceRefresh = true,将执行网络调用以刷新收据。
  • 如果用户未登录到App Store,StoreKit将显示提示,要求用户登录到iTunes Store
  • 如果用户输入有效的凭证,收据将被刷新。
  • 如果用户取消,收据刷新将失败并显示一个< strong>无法连接到iTunes Store的错误。

如果fetchReceipt成功,它将以字符串形式返回加密的收据。因此,需要< strong>验证步骤来生成所有可读的收据字段。这可以通过多种方式完成

  1. 通过AppleReceiptValidator验证Apple(请参阅下面的< code>verifyReceipt)。
  2. 执行本地收据验证(参阅 #101)。
  3. 发送收据数据并在服务器上验证。

验证收据

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

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)")
    }
}

备注

  • 此方法基于 fetchReceipt,并适用于上面讨论的相同刷新逻辑。
  • AppleReceiptValidator 是一个 参考实现,它通过 Apple 验证收据并导致网络调用。这容易受到中间人攻击。
  • 您应该通过在本地验证您的收据或发送加密的收据数据并在您的服务器上验证它来实现您的安全逻辑。
  • 本地收据验证尚未实现(有关详细信息,请参阅 问题 #101)。
  • 您可以通过遵守 ReceiptValidator 协议并将其实例传递给 verifyReceipt 来实现您自己的收据验证器。

验证购买和订阅

一旦您使用 verifyReceipt 方法检索了收据,您就可以通过产品标识符验证您的购买和订阅。

尚不支持在一次调用中验证多个购买和订阅(有关更多详细,请参阅 问题 #194)。

如果您需要验证多个购买/订阅,您可以选择以下方法之一:

  • 手动解析 verifyReceipt 返回的收据字典
  • 使用不同的产品标识符多次调用 verifyPurchaseverifySubscription

验证购买

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 - 处理订阅

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

当找到与特定产品ID相关的订阅时,它们会按expiryDate排序并作为ReceiptItem数组返回,最新的订阅排在前面。

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)")
    }
}

自动续订

let purchaseResult = SwiftyStoreKit.verifySubscription(
            ofType: .autoRenewable,
            productId: "com.musevisions.SwiftyStoreKit.Subscription",
            inReceipt: receipt)

非自动续订

// validDuration: time interval in seconds
let purchaseResult = SwiftyStoreKit.verifySubscription(
            ofType: .nonRenewing(validDuration: 3600 * 24 * 30),
            productId: "com.musevisions.SwiftyStoreKit.Subscription",
            inReceipt: receipt)

备注

  • 到期日期是根据收据日期计算的。这是最近一次成功调用verifyReceipt的日期。
  • 在沙盒模式下购买订阅时,为了测试目的,到期日期会在购买日期后的几分钟内设置。

购买和验证订阅

可以使用verifySubscription方法与purchaseProduct方法一起使用来购买订阅并检查其到期日期,如下所示

let productId = "your-product-id"
SwiftyStoreKit.purchaseProduct(productId, atomically: true) { result in
    
    if case .success(let purchase) = result {
        // Deliver content from server, then:
        if purchase.needsFinishTransaction {
            SwiftyStoreKit.finishTransaction(purchase.transaction)
        }
        
        let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
        SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
            
            if case .success(let receipt) = result {
                let purchaseResult = SwiftyStoreKit.verifySubscription(
                    ofType: .autoRenewable,
                    productId: productId,
                    inReceipt: receipt)
                
                switch purchaseResult {
                case .purchased(let expiryDate, let receiptItems):
                    print("Product is valid until \(expiryDate)")
                case .expired(let expiryDate, let receiptItems):
                    print("Product is expired since \(expiryDate)")
                case .notPurchased:
                    print("This product has never been purchased")
                }

            } else {
                // receipt verification error
            }
        }
    } else {
        // purchase error
    }
}

订阅组

请参阅 Apple Docs - Offering Subscriptions

订阅组是一组你可以创建的应用内购买,以向用户提供一系列内容提供、服务级别或持续时间,以满足他们的需求。用户一次只能购买订阅组内的一个订阅。如果用户想要购买多于一个类型的订阅——例如,在流媒体应用中订阅多个频道——您可以将这些应用内购买放在不同的订阅组中。

您可以使用verifySubscriptions方法验证同一组内的所有订阅

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
    case .success(let receipt):
        let productIds = Set([ "com.musevisions.SwiftyStoreKit.Weekly",
                               "com.musevisions.SwiftyStoreKit.Monthly",
                               "com.musevisions.SwiftyStoreKit.Yearly" ])
        let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
        switch purchaseResult {
        case .purchased(let expiryDate, let items):
            print("\(productIds) are valid until \(expiryDate)\n\(items)\n")
        case .expired(let expiryDate, let items):
            print("\(productIds) are expired since \(expiryDate)\n\(items)\n")
        case .notPurchased:
            print("The user has never purchased \(productIds)")
        }
    case .error(let error):
        print("Receipt verification failed: \(error)")
    }
}

获取唯一购买标识符

您可以使用 getDistinctPurchaseIds 方法检索所有产品标识符

let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
    switch result {
    case .success(let receipt):
        let productIds = SwiftyStoreKit.getDistinctPurchaseIds(inReceipt receipt: ReceiptInfo)
        let purchaseResult = SwiftyStoreKit.verifySubscriptions(productIds: productIds, inReceipt: receipt)
        switch purchaseResult {
        case .purchased(let expiryDate, let items):
            print("\(productIds) are valid until \(expiryDate)\n\(items)\n")
        case .expired(let expiryDate, let items):
            print("\(productIds) are expired since \(expiryDate)\n\(items)\n")
        case .notPurchased:
            print("The user has never purchased \(productIds)")
        }
    case .error(let error):
        print("Receipt verification failed: \(error)")
    }
}

备注

该框架在现有的 StoreKit 框架之上提供了一个简单的基于块的 API,并具有强大的错误处理功能。它不本地持久化应用内购买数据。这取决于客户端使用所选的存储解决方案(例如 NSUserDefaults、CoreData、Keychain)来完成此项工作。

Swift 2.x / 3.x / 4.x / 5.x

语言 分支 Pod 版本 Xcode 版本
Swift 5.x 主分支 >= 0.15.0 Xcode 10.2 或更高版本
Swift 4.x 主分支 >= 0.10.4 Xcode 9 或更高版本
Swift 3.x 主分支 >= 0.5.x Xcode 8.x
Swift 2.3 swift-2.3 0.4.x Xcode 8, Xcode 7.3.x
Swift 2.2 swift-2.2 0.3.x Xcode 7.3.x

变更日志

请参阅发布页面

示例代码

该项目包括演示应用,针对 iOS针对 macOS,展示了如何使用 SwiftyStoreKit。请注意,演示应用中预注册的应用内购买仅用于说明目的,可能无法按预期运行,因为 iTunes Connect 可能会使其失效。

必备阅读

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

故障排除

视频教程

Jared Davidson:应用内购买!(Swift 3 在 Xcode 中:Swifty Store Kit)

@rebeloper: 完美内购指南

付款流程:实现细节

为了完成购买,需要两个操作

  • 执行 SKProductRequest 以获取与产品标识符对应的 SKProduct

  • 提交付款并监听 SKPaymentQueue 上更新的交易。

框架负责缓存 SKProducts,以便将来对同一 SKProduct 的请求不需要执行新的 SKProductRequest

付款队列

以下列表概述了 SwiftyStoreKit 如何处理请求。

  • SKPaymentQueue 用于排队付款或恢复购买请求。
  • 付款按顺序串行处理,并需要用户交互。
  • 恢复购买请求不需要用户交互,并且可以跳过队列。
  • SKPaymentQueue 拒绝重复的恢复购买调用。
  • 失败的交易仅属于队列中的付款请求。
  • 当恢复购买请求失败时,总是调用 restoreCompletedTransactionsFailedWithError
  • 当恢复购买请求成功时,总是调用 paymentQueueRestoreCompletedTransactionsFinished 随着零个或多个更新交易的完成。
  • 需要一个完整的交易处理程序来捕获在应用程序未运行时更新的任何交易。
  • 当应用程序启动时注册完整的交易处理程序可确保清除任何挂起的交易。
  • 如果缺少完整的交易处理程序,挂起的交易可能会误分配给任何新的付款或恢复购买。

事务更新处理的顺序是

  1. 付款(transactionState: .purchased.failed 对应的产品标识符)
  2. 恢复购买(transactionState: .restored,或 restoreCompletedTransactionsFailedWithError,或 paymentQueueRestoreCompletedTransactionsFinished
  3. 完整的交易(transactionState: .purchased.failed.restored.deferred

忽略任何状态为 .purchasing 的交易。

查看 此拉取请求 以获取有关付款流程如何实现的完整细节。

信用

非常感谢phimage添加了对macOS的支持以及收据验证。

使用SwiftyStoreKit的应用

在这里展示使用SwiftyStoreKit的应用会很好。欢迎提交pull request :)

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

许可证

版权所有 (c) 2015-2018 Andrea Bizzotto [email protected]

授予任何人免费获得本软件及其相关文档文件(以下简称“软件”)的副本的权利,不受限制地处理软件,包括但不限于以下权利:使用、复制、修改、合并、发布、分发、再许可和/或出售软件的副本,并允许将软件提供给他人,使他人可以从事上述行为,但须遵守以下条件:

上述版权通知和本许可通知应包含在软件的任何副本或主要内容中。

软件按“现状”提供,不提供任何明示或暗示的保证,包括但不限于对适销性、适用于特定目的和不侵权的保证。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任(无论源于合同、侵权或任何其他原因),因软件、软件的使用或其他关于软件的操作而产生的。