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

Restore Purchase call to return all restored purchase IDs at once #19

Merged
merged 3 commits into from
Apr 10, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1") { res
### Restore previous purchases

```swift
SwiftyStoreKit.restorePurchases() { result in
switch result {
case .Success(let productId):
print("Restore Success: \(productId)")
case .NothingToRestore:
SwiftyStoreKit.restorePurchases() { results in
if results.restoreFailedProducts.count > 0 {
print("Restore Failed: \(results.restoreFailedProducts)")
}
else if results.restoredProductIds.count > 0 {
print("Restore Success: \(results.restoredProductIds)")
}
else {
print("Nothing to Restore")
case .Error(let error):
print("Restore Failed: \(error)")
}
}
```
Expand Down
23 changes: 12 additions & 11 deletions SwiftyStoreDemo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ class ViewController: UIViewController {
@IBAction func restorePurchases() {

NetworkActivityIndicatorManager.networkOperationStarted()
SwiftyStoreKit.restorePurchases() { result in
SwiftyStoreKit.restorePurchases() { results in
NetworkActivityIndicatorManager.networkOperationFinished()

self.showAlert(self.alertForRestorePurchases(result))
self.showAlert(self.alertForRestorePurchases(results))
}
}

Expand Down Expand Up @@ -154,18 +154,19 @@ extension ViewController {
}
}

func alertForRestorePurchases(result: SwiftyStoreKit.RestoreResult) -> UIAlertController {

switch result {
case .Success(let productId):
print("Restore Success: \(productId)")
func alertForRestorePurchases(results: SwiftyStoreKit.RestoreResults) -> UIAlertController {

if results.restoreFailedProducts.count > 0 {
print("Restore Failed: \(results.restoreFailedProducts)")
return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
}
else if results.restoredProductIds.count > 0 {
print("Restore Success: \(results.restoredProductIds)")
return alertWithTitle("Purchases Restored", message: "All purchases have been restored")
case .NothingToRestore:
}
else {
print("Nothing to Restore")
return alertWithTitle("Nothing to restore", message: "No previous purchases were found")
case .Error(let error):
print("Restore Failed: \(error)")
return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
}
}

Expand Down
35 changes: 17 additions & 18 deletions SwiftyStoreKit/InAppProductPurchaseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
enum TransactionResult {
case Purchased(productId: String)
case Restored(productId: String)
case NothingToRestore
case Failed(error: NSError)
}

typealias RequestCallback = (result: TransactionResult) -> ()
typealias RequestCallback = (results: [TransactionResult]) -> ()
private let callback: RequestCallback
private var purchases : [PaymentTransactionState: [String]] = [:]

Expand Down Expand Up @@ -88,6 +87,8 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
// MARK: SKPaymentTransactionObserver
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

var transactionResults: [TransactionResult] = []

for transaction in transactions {

#if os(iOS)
Expand All @@ -98,22 +99,16 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {

switch transactionState {
case .Purchased:
dispatch_async(dispatch_get_main_queue()) {
self.callback(result: .Purchased(productId: transaction.payment.productIdentifier))
}
transactionResults.append(.Purchased(productId: transaction.payment.productIdentifier))
paymentQueue.finishTransaction(transaction)
case .Failed:
dispatch_async(dispatch_get_main_queue()) {
// It appears that in some edge cases transaction.error is nil here. Since returning an associated error is
// mandatory, return a default one if needed
let altError = NSError(domain: SKErrorDomain, code: 0, userInfo: [ NSLocalizedDescriptionKey: "Unknown error" ])
self.callback(result: .Failed(error: transaction.error ?? altError))
}
// It appears that in some edge cases transaction.error is nil here. Since returning an associated error is
// mandatory, return a default one if needed
let altError = NSError(domain: SKErrorDomain, code: 0, userInfo: [ NSLocalizedDescriptionKey: "Unknown error" ])
transactionResults.append(.Failed(error: transaction.error ?? altError))
paymentQueue.finishTransaction(transaction)
case .Restored:
dispatch_async(dispatch_get_main_queue()) {
self.callback(result: .Restored(productId: transaction.payment.productIdentifier))
}
transactionResults.append(.Restored(productId: transaction.payment.productIdentifier))
paymentQueue.finishTransaction(transaction)
case .Purchasing:
// In progress: do nothing
Expand All @@ -129,6 +124,11 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
purchases[transactionState] = [ transaction.payment.productIdentifier ]
}
}
if transactionResults.count > 0 {
dispatch_async(dispatch_get_main_queue()) {
self.callback(results: transactionResults)
}
}
}

func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
Expand All @@ -138,22 +138,21 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
func paymentQueue(queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {

dispatch_async(dispatch_get_main_queue()) {
self.callback(result: .Failed(error: error))
self.callback(results: [.Failed(error: error)])
}
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
if let product = self.product, productIdentifier = product._productIdentifier {
self.callback(result: .Restored(productId: productIdentifier))
self.callback(results: [.Restored(productId: productIdentifier)])
return
}
// This method will be called after all purchases have been restored (includes the case of no purchases)
guard let restored = purchases[.Restored] where restored.count > 0 else {

self.callback(result: .NothingToRestore)
self.callback(results: [])
return
}
//print("\(restored)")
}

func paymentQueue(queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
Expand Down
53 changes: 28 additions & 25 deletions SwiftyStoreKit/SwiftyStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,17 @@ public class SwiftyStoreKit {
case Success(product: SKProduct)
case Error(error: ErrorType)
}
public enum RestoreResult {
case Success(productId: String)
case Error(error: ErrorType)
case NothingToRestore
public struct RestoreResults {
public let restoredProductIds: [String]
public let restoreFailedProducts: [(ErrorType, String?)]
}
public enum RefreshReceiptResult {
case Success
case Error(error: ErrorType)
}
public enum InternalErrorCode: Int {
case RestoredPurchaseWhenPurchasing = 0
case NothingToRestoreWhenPurchasing = 1
case PurchasedWhenRestoringPurchase = 2
case PurchasedWhenRestoringPurchase = 1
}

// MARK: Singleton
Expand Down Expand Up @@ -115,13 +113,14 @@ public class SwiftyStoreKit {
}
}

public class func restorePurchases(completion: (result: RestoreResult) -> ()) {
public class func restorePurchases(completion: (results: RestoreResults) -> ()) {

sharedInstance.restoreRequest = InAppProductPurchaseRequest.restorePurchases() { result in
// Called multiple
sharedInstance.restoreRequest = InAppProductPurchaseRequest.restorePurchases() { results in

sharedInstance.restoreRequest = nil
let returnValue = sharedInstance.processRestoreResult(result)
completion(result: returnValue)
let results = sharedInstance.processRestoreResults(results)
completion(results: results)
}
}

Expand Down Expand Up @@ -177,13 +176,15 @@ public class SwiftyStoreKit {
return
}

inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product) { result in
inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product) { results in

if let productIdentifier = product._productIdentifier {
self.inflightPurchases[productIdentifier] = nil
}
let returnValue = self.processPurchaseResult(result)
completion(result: returnValue)
if let purchasedProductTransaction = results.first {
let returnValue = self.processPurchaseResult(purchasedProductTransaction)
completion(result: returnValue)
}
}
}

Expand All @@ -195,24 +196,26 @@ public class SwiftyStoreKit {
return .Error(error: .Failed(error: error))
case .Restored(let productId):
return .Error(error: .Failed(error: storeInternalError(code: InternalErrorCode.RestoredPurchaseWhenPurchasing.rawValue, description: "Cannot restore product \(productId) from purchase path")))
case .NothingToRestore:
return .Error(error: .Failed(error: storeInternalError(code: InternalErrorCode.NothingToRestoreWhenPurchasing.rawValue, description: "Cannot restore product from purchase path")))
}
}

private func processRestoreResult(result: InAppProductPurchaseRequest.TransactionResult) -> RestoreResult {
switch result {
case .Purchased(let productId):
return .Error(error: storeInternalError(code: InternalErrorCode.PurchasedWhenRestoringPurchase.rawValue, description: "Cannot purchase product \(productId) from restore purchases path"))
case .Failed(let error):
return .Error(error: error)
case .Restored(let productId):
return .Success(productId: productId)
case .NothingToRestore:
return .NothingToRestore
private func processRestoreResults(results: [InAppProductPurchaseRequest.TransactionResult]) -> RestoreResults {
var restoredProductIds: [String] = []
var restoreFailedProducts: [(ErrorType, String?)] = []
for result in results {
switch result {
case .Purchased(let productId):
restoreFailedProducts.append((storeInternalError(code: InternalErrorCode.PurchasedWhenRestoringPurchase.rawValue, description: "Cannot purchase product \(productId) from restore purchases path"), productId))
case .Failed(let error):
restoreFailedProducts.append((error, nil))
case .Restored(let productId):
restoredProductIds.append(productId)
}
}
return RestoreResults(restoredProductIds: restoredProductIds, restoreFailedProducts: restoreFailedProducts)
}


// http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/
private func requestProduct(productId: String, completion: (result: (() throws -> SKProduct)) -> ()) -> () {

Expand Down
23 changes: 12 additions & 11 deletions SwiftyStoreOSXDemo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ class ViewController: NSViewController {

@IBAction func restorePurchases(sender: AnyObject?) {

SwiftyStoreKit.restorePurchases() { result in
SwiftyStoreKit.restorePurchases() { results in

self.showAlert(self.alertForRestorePurchases(result))
self.showAlert(self.alertForRestorePurchases(results))
}
}

Expand Down Expand Up @@ -140,21 +140,22 @@ extension ViewController {
}
}

func alertForRestorePurchases(result: SwiftyStoreKit.RestoreResult) -> NSAlert {
func alertForRestorePurchases(results: SwiftyStoreKit.RestoreResults) -> NSAlert {

switch result {
case .Success(let productId):
print("Restore Success: \(productId)")
if results.restoreFailedProducts.count > 0 {
print("Restore Failed: \(results.restoreFailedProducts)")
return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
}
else if results.restoredProductIds.count > 0 {
print("Restore Success: \(results.restoredProductIds)")
return alertWithTitle("Purchases Restored", message: "All purchases have been restored")
case .NothingToRestore:
}
else {
print("Nothing to Restore")
return alertWithTitle("Nothing to restore", message: "No previous purchases were found")
case .Error(let error):
print("Restore Failed: \(error)")
return alertWithTitle("Restore failed", message: "Unknown error. Please contact support")
}
}

func alertForVerifyReceipt(result: SwiftyStoreKit.VerifyReceiptResult) -> NSAlert {

switch result {
Expand Down