Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for downloading content hosted by Apple #343

Merged
merged 8 commits into from
Jan 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
19 changes: 16 additions & 3 deletions SwiftyStoreKit-iOS-Demo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

completeIAPTransactions()
setupIAP()

return true
}

func completeIAPTransactions() {
func setupIAP() {

SwiftyStoreKit.completeTransactions(atomically: true) { purchases in

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)
}
Expand All @@ -54,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)
}
}
}
}
15 changes: 12 additions & 3 deletions SwiftyStoreKit-iOS-Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ class ViewController: UIViewController {
NetworkActivityIndicatorManager.networkOperationFinished()

if case .success(let purchase) = result {
let downloads = purchase.transaction.downloads
if !downloads.isEmpty {
SwiftyStoreKit.start(downloads)
}
// Deliver content from server, then:
if purchase.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(purchase.transaction)
Expand All @@ -150,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))
}
Expand Down
22 changes: 21 additions & 1 deletion SwiftyStoreKit/PaymentQueueController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ public protocol PaymentQueue: class {
func remove(_ observer: SKPaymentTransactionObserver)

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)
Expand Down Expand Up @@ -151,7 +156,21 @@ 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?

// MARK: SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
Expand Down Expand Up @@ -210,6 +229,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 {
Expand Down
2 changes: 2 additions & 0 deletions SwiftyStoreKit/SwiftyStoreKit+Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -80,6 +81,7 @@ public struct RestoreResults {
}

public typealias ShouldAddStorePaymentHandler = (_ payment: SKPayment, _ product: SKProduct) -> Bool
public typealias UpdatedDownloadsHandler = (_ downloads: [SKDownload]) -> Void

// MARK: Receipt verification

Expand Down
22 changes: 22 additions & 0 deletions SwiftyStoreKit/SwiftyStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,28 @@ extension SwiftyStoreKit {
sharedInstance.paymentQueueController.shouldAddStorePaymentHandler = shouldAddStorePaymentHandler
}
}

/**
* Register a handler for paymentQueue(_:updatedDownloads:)
*/
public static var updatedDownloadsHandler: UpdatedDownloadsHandler? {
didSet {
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 {
Expand Down
16 changes: 16 additions & 0 deletions SwiftyStoreKitTests/PaymentQueueSpy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {

}
}