From c0df52c28e382a0e1a028bede93cd69c59b5a797 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Fri, 26 Jan 2018 21:36:12 +0000 Subject: [PATCH 1/8] Expose downloads array when a product is purchased. Add updatedDownloadsHandler block to be called to process download updates. --- SwiftyStoreKit/PaymentQueueController.swift | 6 ++++-- SwiftyStoreKit/PaymentsController.swift | 2 +- SwiftyStoreKit/SwiftyStoreKit+Types.swift | 3 ++- SwiftyStoreKit/SwiftyStoreKit.swift | 15 ++++++++++++--- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/SwiftyStoreKit/PaymentQueueController.swift b/SwiftyStoreKit/PaymentQueueController.swift index f50d5701..8923cd7a 100644 --- a/SwiftyStoreKit/PaymentQueueController.swift +++ b/SwiftyStoreKit/PaymentQueueController.swift @@ -36,7 +36,7 @@ protocol TransactionController { } public enum TransactionResult { - case purchased(purchase: PurchaseDetails) + case purchased(purchase: PurchaseDetails, downloads: [SKDownload]) case restored(purchase: Purchase) case failed(error: SKError) } @@ -47,7 +47,7 @@ public protocol PaymentQueue: class { func remove(_ observer: SKPaymentTransactionObserver) func add(_ payment: SKPayment) - + func restoreCompletedTransactions(withApplicationUsername username: String?) func finishTransaction(_ transaction: SKPaymentTransaction) @@ -152,6 +152,7 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver { } var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler? + var updatedDownloadsHandler: UpdatedDownloadsHandler? // MARK: SKPaymentTransactionObserver func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { @@ -210,6 +211,7 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) { + updatedDownloadsHandler?(downloads) } func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { diff --git a/SwiftyStoreKit/PaymentsController.swift b/SwiftyStoreKit/PaymentsController.swift index a9916e0e..f2acedfe 100644 --- a/SwiftyStoreKit/PaymentsController.swift +++ b/SwiftyStoreKit/PaymentsController.swift @@ -74,7 +74,7 @@ class PaymentsController: TransactionController { if transactionState == .purchased { let purchase = PurchaseDetails(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, product: payment.product, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !payment.atomically) - payment.callback(.purchased(purchase: purchase)) + payment.callback(.purchased(purchase: purchase, downloads: transaction.downloads)) if payment.atomically { paymentQueue.finishTransaction(transaction) diff --git a/SwiftyStoreKit/SwiftyStoreKit+Types.swift b/SwiftyStoreKit/SwiftyStoreKit+Types.swift index bb3a686d..646ef2c9 100644 --- a/SwiftyStoreKit/SwiftyStoreKit+Types.swift +++ b/SwiftyStoreKit/SwiftyStoreKit+Types.swift @@ -69,7 +69,7 @@ public struct RetrieveResults { // Purchase result public enum PurchaseResult { - case success(purchase: PurchaseDetails) + case success(purchase: PurchaseDetails, downloads: [SKDownload]) case error(error: SKError) } @@ -80,6 +80,7 @@ public struct RestoreResults { } public typealias ShouldAddStorePaymentHandler = (_ payment: SKPayment, _ product: SKProduct) -> Bool +public typealias UpdatedDownloadsHandler = (_ downloads: [SKDownload]) -> Void // MARK: Receipt verification diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index d812893b..8a20fac9 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -95,8 +95,8 @@ public class SwiftyStoreKit { private func processPurchaseResult(_ result: TransactionResult) -> PurchaseResult { switch result { - case .purchased(let purchase): - return .success(purchase: purchase) + case .purchased(let purchase, let downloads): + return .success(purchase: purchase, downloads: downloads) case .failed(let error): return .error(error: error) case .restored(let purchase): @@ -109,7 +109,7 @@ public class SwiftyStoreKit { var restoreFailedPurchases: [(SKError, String?)] = [] for result in results { switch result { - case .purchased(let purchase): + case .purchased(let purchase, _): let error = storeInternalError(description: "Cannot purchase product \(purchase.productId) from restore purchases path") restoreFailedPurchases.append((error, purchase.productId)) case .failed(let error): @@ -216,6 +216,15 @@ extension SwiftyStoreKit { sharedInstance.paymentQueueController.shouldAddStorePaymentHandler = shouldAddStorePaymentHandler } } + + /** + * Register a handler for paymentQueue(_:updatedDownloads:) + */ + public static var updatedDownloadsHandler: UpdatedDownloadsHandler? { + didSet { + sharedInstance.paymentQueueController.updatedDownloadsHandler = updatedDownloadsHandler + } + } } extension SwiftyStoreKit { From 469476afc7e0dae7f7f4ee1cb50116ba8e9831b8 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Fri, 26 Jan 2018 21:45:16 +0000 Subject: [PATCH 2/8] Add start/pause/resume/cancel methods for downloads --- SwiftyStoreKit/PaymentQueueController.swift | 18 ++++++++++++++++++ SwiftyStoreKit/SwiftyStoreKit.swift | 13 +++++++++++++ 2 files changed, 31 insertions(+) diff --git a/SwiftyStoreKit/PaymentQueueController.swift b/SwiftyStoreKit/PaymentQueueController.swift index 8923cd7a..a1ce1f75 100644 --- a/SwiftyStoreKit/PaymentQueueController.swift +++ b/SwiftyStoreKit/PaymentQueueController.swift @@ -48,6 +48,11 @@ public protocol PaymentQueue: class { func add(_ payment: SKPayment) + func start(_ downloads: [SKDownload]) + func pause(_ downloads: [SKDownload]) + func resume(_ downloads: [SKDownload]) + func cancel(_ downloads: [SKDownload]) + func restoreCompletedTransactions(withApplicationUsername username: String?) func finishTransaction(_ transaction: SKPaymentTransaction) @@ -151,6 +156,19 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver { paymentQueue.finishTransaction(skTransaction) } + func start(_ downloads: [SKDownload]) { + paymentQueue.start(downloads) + } + func pause(_ downloads: [SKDownload]) { + paymentQueue.pause(downloads) + } + func resume(_ downloads: [SKDownload]) { + paymentQueue.resume(downloads) + } + func cancel(_ downloads: [SKDownload]) { + paymentQueue.cancel(downloads) + } + var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler? var updatedDownloadsHandler: UpdatedDownloadsHandler? diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index 8a20fac9..e4b7a4a0 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -225,6 +225,19 @@ extension SwiftyStoreKit { sharedInstance.paymentQueueController.updatedDownloadsHandler = updatedDownloadsHandler } } + + public class func start(_ downloads: [SKDownload]) { + sharedInstance.paymentQueueController.start(downloads) + } + public class func pause(_ downloads: [SKDownload]) { + sharedInstance.paymentQueueController.pause(downloads) + } + public class func resume(_ downloads: [SKDownload]) { + sharedInstance.paymentQueueController.resume(downloads) + } + public class func cancel(_ downloads: [SKDownload]) { + sharedInstance.paymentQueueController.cancel(downloads) + } } extension SwiftyStoreKit { From 756e9fe48daa491bf774c77e1320cd65d9115440 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Fri, 26 Jan 2018 21:46:44 +0000 Subject: [PATCH 3/8] Updated main app to show how to use updatedDownloadsHandler and the new download methods --- SwiftyStoreKit-iOS-Demo/AppDelegate.swift | 8 ++++++++ SwiftyStoreKit-iOS-Demo/ViewController.swift | 7 +++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift index d83ed972..91affec0 100644 --- a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift +++ b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift @@ -33,6 +33,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { completeIAPTransactions() + + SwiftyStoreKit.updatedDownloadsHandler = { downloads in + + let finishedDownloadsCount = downloads.filter { $0.downloadState == .finished }.count + if finishedDownloadsCount == downloads.count { + SwiftyStoreKit.finishTransaction(downloads[0].transaction) + } + } return true } diff --git a/SwiftyStoreKit-iOS-Demo/ViewController.swift b/SwiftyStoreKit-iOS-Demo/ViewController.swift index 4c1f8c99..591094ce 100644 --- a/SwiftyStoreKit-iOS-Demo/ViewController.swift +++ b/SwiftyStoreKit-iOS-Demo/ViewController.swift @@ -132,7 +132,10 @@ class ViewController: UIViewController { SwiftyStoreKit.purchaseProduct(appBundleId + "." + purchase.rawValue, atomically: atomically) { result in NetworkActivityIndicatorManager.networkOperationFinished() - if case .success(let purchase) = result { + if case .success(let purchase, let downloads) = result { + if !downloads.isEmpty { + SwiftyStoreKit.start(downloads) + } // Deliver content from server, then: if purchase.needsFinishTransaction { SwiftyStoreKit.finishTransaction(purchase.transaction) @@ -267,7 +270,7 @@ extension ViewController { // swiftlint:disable cyclomatic_complexity func alertForPurchaseResult(_ result: PurchaseResult) -> UIAlertController? { switch result { - case .success(let purchase): + case .success(let purchase, _): print("Purchase Success: \(purchase.productId)") return nil case .error(let error): From 49bd0dd0c3d83ce4f8eb20a2f69d91be8471988b Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Sat, 27 Jan 2018 08:49:07 +0000 Subject: [PATCH 4/8] Access downloads array from transaction. This enables downloads in all purchase paths (completeTransactions, purchaseProduct, restorePurchases) --- SwiftyStoreKit-iOS-Demo/AppDelegate.swift | 5 ++++- SwiftyStoreKit-iOS-Demo/ViewController.swift | 16 +++++++++++----- SwiftyStoreKit/PaymentQueueController.swift | 2 +- SwiftyStoreKit/PaymentsController.swift | 2 +- SwiftyStoreKit/SwiftyStoreKit+Types.swift | 3 ++- SwiftyStoreKit/SwiftyStoreKit.swift | 6 +++--- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift index 91affec0..1a797101 100644 --- a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift +++ b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift @@ -52,7 +52,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { for purchase in purchases { switch purchase.transaction.transactionState { case .purchased, .restored: - if purchase.needsFinishTransaction { + let downloads = purchase.transaction.downloads + if !downloads.isEmpty { + SwiftyStoreKit.start(downloads) + } else if purchase.needsFinishTransaction { // Deliver content from server, then: SwiftyStoreKit.finishTransaction(purchase.transaction) } diff --git a/SwiftyStoreKit-iOS-Demo/ViewController.swift b/SwiftyStoreKit-iOS-Demo/ViewController.swift index 591094ce..7a56f904 100644 --- a/SwiftyStoreKit-iOS-Demo/ViewController.swift +++ b/SwiftyStoreKit-iOS-Demo/ViewController.swift @@ -132,7 +132,8 @@ class ViewController: UIViewController { SwiftyStoreKit.purchaseProduct(appBundleId + "." + purchase.rawValue, atomically: atomically) { result in NetworkActivityIndicatorManager.networkOperationFinished() - if case .success(let purchase, let downloads) = result { + if case .success(let purchase) = result { + let downloads = purchase.transaction.downloads if !downloads.isEmpty { SwiftyStoreKit.start(downloads) } @@ -153,9 +154,14 @@ class ViewController: UIViewController { SwiftyStoreKit.restorePurchases(atomically: true) { results in NetworkActivityIndicatorManager.networkOperationFinished() - for purchase in results.restoredPurchases where purchase.needsFinishTransaction { - // Deliver content from server, then: - SwiftyStoreKit.finishTransaction(purchase.transaction) + for purchase in results.restoredPurchases { + let downloads = purchase.transaction.downloads + if !downloads.isEmpty { + SwiftyStoreKit.start(downloads) + } else if purchase.needsFinishTransaction { + // Deliver content from server, then: + SwiftyStoreKit.finishTransaction(purchase.transaction) + } } self.showAlert(self.alertForRestorePurchases(results)) } @@ -270,7 +276,7 @@ extension ViewController { // swiftlint:disable cyclomatic_complexity func alertForPurchaseResult(_ result: PurchaseResult) -> UIAlertController? { switch result { - case .success(let purchase, _): + case .success(let purchase): print("Purchase Success: \(purchase.productId)") return nil case .error(let error): diff --git a/SwiftyStoreKit/PaymentQueueController.swift b/SwiftyStoreKit/PaymentQueueController.swift index a1ce1f75..0ac196e2 100644 --- a/SwiftyStoreKit/PaymentQueueController.swift +++ b/SwiftyStoreKit/PaymentQueueController.swift @@ -36,7 +36,7 @@ protocol TransactionController { } public enum TransactionResult { - case purchased(purchase: PurchaseDetails, downloads: [SKDownload]) + case purchased(purchase: PurchaseDetails) case restored(purchase: Purchase) case failed(error: SKError) } diff --git a/SwiftyStoreKit/PaymentsController.swift b/SwiftyStoreKit/PaymentsController.swift index f2acedfe..a9916e0e 100644 --- a/SwiftyStoreKit/PaymentsController.swift +++ b/SwiftyStoreKit/PaymentsController.swift @@ -74,7 +74,7 @@ class PaymentsController: TransactionController { if transactionState == .purchased { let purchase = PurchaseDetails(productId: transactionProductIdentifier, quantity: transaction.payment.quantity, product: payment.product, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !payment.atomically) - payment.callback(.purchased(purchase: purchase, downloads: transaction.downloads)) + payment.callback(.purchased(purchase: purchase)) if payment.atomically { paymentQueue.finishTransaction(transaction) diff --git a/SwiftyStoreKit/SwiftyStoreKit+Types.swift b/SwiftyStoreKit/SwiftyStoreKit+Types.swift index 646ef2c9..df349151 100644 --- a/SwiftyStoreKit/SwiftyStoreKit+Types.swift +++ b/SwiftyStoreKit/SwiftyStoreKit+Types.swift @@ -55,6 +55,7 @@ public protocol PaymentTransaction { var transactionDate: Date? { get } var transactionState: SKPaymentTransactionState { get } var transactionIdentifier: String? { get } + var downloads: [SKDownload] { get } } // Add PaymentTransaction conformance to SKPaymentTransaction @@ -69,7 +70,7 @@ public struct RetrieveResults { // Purchase result public enum PurchaseResult { - case success(purchase: PurchaseDetails, downloads: [SKDownload]) + case success(purchase: PurchaseDetails) case error(error: SKError) } diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index e4b7a4a0..70d3442d 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -95,8 +95,8 @@ public class SwiftyStoreKit { private func processPurchaseResult(_ result: TransactionResult) -> PurchaseResult { switch result { - case .purchased(let purchase, let downloads): - return .success(purchase: purchase, downloads: downloads) + case .purchased(let purchase): + return .success(purchase: purchase) case .failed(let error): return .error(error: error) case .restored(let purchase): @@ -109,7 +109,7 @@ public class SwiftyStoreKit { var restoreFailedPurchases: [(SKError, String?)] = [] for result in results { switch result { - case .purchased(let purchase, _): + case .purchased(let purchase): let error = storeInternalError(description: "Cannot purchase product \(purchase.productId) from restore purchases path") restoreFailedPurchases.append((error, purchase.productId)) case .failed(let error): From 9d0694c4116c3397c41227e4f077fd1f4265d510 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Sat, 27 Jan 2018 08:50:07 +0000 Subject: [PATCH 5/8] Make unit tests compile --- SwiftyStoreKitTests/PaymentQueueSpy.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/SwiftyStoreKitTests/PaymentQueueSpy.swift b/SwiftyStoreKitTests/PaymentQueueSpy.swift index d66fc4cc..f763989c 100644 --- a/SwiftyStoreKitTests/PaymentQueueSpy.swift +++ b/SwiftyStoreKitTests/PaymentQueueSpy.swift @@ -44,4 +44,20 @@ class PaymentQueueSpy: PaymentQueue { finishTransactionCalledCount += 1 } + + func start(_ downloads: [SKDownload]) { + + } + + func pause(_ downloads: [SKDownload]) { + + } + + func resume(_ downloads: [SKDownload]) { + + } + + func cancel(_ downloads: [SKDownload]) { + + } } From 2dca29394cbcc25d6056078cb6b68bc77e525673 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Sat, 27 Jan 2018 08:55:24 +0000 Subject: [PATCH 6/8] Clean up AppDelegate --- SwiftyStoreKit-iOS-Demo/AppDelegate.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift index 1a797101..6f3b6eed 100644 --- a/SwiftyStoreKit-iOS-Demo/AppDelegate.swift +++ b/SwiftyStoreKit-iOS-Demo/AppDelegate.swift @@ -32,20 +32,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { - completeIAPTransactions() - - SwiftyStoreKit.updatedDownloadsHandler = { downloads in - - let finishedDownloadsCount = downloads.filter { $0.downloadState == .finished }.count - if finishedDownloadsCount == downloads.count { - SwiftyStoreKit.finishTransaction(downloads[0].transaction) - } - } + setupIAP() return true } - func completeIAPTransactions() { + func setupIAP() { SwiftyStoreKit.completeTransactions(atomically: true) { purchases in @@ -65,5 +57,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } } + + SwiftyStoreKit.updatedDownloadsHandler = { downloads in + + // contentURL is not nil if downloadState == .finished + let contentURLs = downloads.flatMap { $0.contentURL } + if contentURLs.count == downloads.count { + print("Saving: \(contentURLs)") + SwiftyStoreKit.finishTransaction(downloads[0].transaction) + } + } } } From 90a72af81bfc64f387c78bccba7f7ce14fb17543 Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Mon, 29 Jan 2018 22:21:25 +0000 Subject: [PATCH 7/8] Update Readme --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index c2e4e02a..cb1a3bf6 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,48 @@ SwiftyStoreKit provides three operations that can be performed **atomically** or * Restoring purchases * Completing transactions on app launch +### Downloading content hosted with Apple + +Quoting Apple Docs: + +> When you create a product in iTunes Connect, you can associate one or more pieces of downloadable content with it. At runtime, when a product is purchased by a user, your app uses SKDownload objects to download the content from the App Store. + +> Your app never directly creates a SKDownload object. Instead, after a payment is processed, your app reads the transaction object’s downloads property to retrieve an array of SKDownload objects associated with the transaction. + +> To download the content, you queue a download object on the payment queue and wait for the content to be downloaded. After a download completes, read the download object’s contentURL property to get a URL to the downloaded content. Your app must process the downloaded file before completing the transaction. For example, it might copy the file into a directory whose contents are persistent. When all downloads are complete, you finish the transaction. After the transaction is finished, the download objects cannot be queued to the payment queue and any URLs to the downloaded content are invalid. + +To start the downloads: + +```swift +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)") + } +} +``` + +To check the updated downloads, setup a `updatedDownloadsHandler` block in your AppDelegate: + +```swift +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) + } +} +``` + +To control the state of the downloads, SwiftyStoreKit offers `start()`, `pause()`, `resume()`, `cancel()` methods. + ## Receipt verification According to [Apple - Delivering Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/DeliverProduct.html#//apple_ref/doc/uid/TP40008267-CH5-SW4): From c6c84eaa1cd86856909e012ad6a0268b5d6d763c Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Mon, 29 Jan 2018 22:25:24 +0000 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90679740..a80b72b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [0.13.0](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.13.0) Add support for downloading content hosted with Apple + +* Add support for downloading content hosted with Apple ([#343](https://github.com/bizz84/SwiftyStoreKit/pull/343), related issue: [#128](https://github.com/bizz84/SwiftyStoreKit/issues/128)) + ## [0.12.1](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.12.1) Assert that `completeTransactions` was called when the app launches. * Assert that `completeTransactions()` was called when the app launches ([#337](https://github.com/bizz84/SwiftyStoreKit/pull/337), related issue: [#287](https://github.com/bizz84/SwiftyStoreKit/issues/287))