Przeglądaj źródła

Merge pull request #138 from bizz84/feature/improve-error-handling

Feature/improve error handling
Andrea Bizzotto 8 lat temu
rodzic
commit
ff7a46d6f3

+ 21 - 3
README.md

@@ -76,7 +76,16 @@ SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", atomi
     case .success(let product):
         print("Purchase Success: \(product.productId)")
     case .error(let error):
-        print("Purchase Failed: \(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")
+        }
     }
 }
 ```
@@ -93,7 +102,16 @@ SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", atomi
         }
         print("Purchase Success: \(product.productId)")
     case .error(let error):
-        print("Purchase Failed: \(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")
+        }
     }
 }
 ```
@@ -213,7 +231,7 @@ SwiftyStoreKit.verifyReceipt(using: appleValidator, password: "your-shared-secre
         case .notPurchased:
             print("The user has never purchased this product")
         }
-    case .Error(let error):
+    case .error(let error):
         print("Receipt verification failed: \(error)")
     }
 }

+ 20 - 12
SwiftyStoreKit-iOS-Demo/ViewController.swift

@@ -86,7 +86,9 @@ class ViewController: UIViewController {
                     SwiftyStoreKit.finishTransaction(product.transaction)
                 }
             }
-            self.showAlert(self.alertForPurchaseResult(result))
+            if let alert = self.alertForPurchaseResult(result) {
+                self.showAlert(alert)
+            }
         }
     }
     
@@ -206,23 +208,29 @@ extension ViewController {
         }
     }
 
-    func alertForPurchaseResult(_ result: PurchaseResult) -> UIAlertController {
+    func alertForPurchaseResult(_ result: PurchaseResult) -> UIAlertController? {
         switch result {
         case .success(let product):
             print("Purchase Success: \(product.productId)")
             return alertWithTitle("Thank You", message: "Purchase completed")
         case .error(let error):
             print("Purchase Failed: \(error)")
-            switch error {
-                case .failed(let error):
-                    if (error as NSError).domain == SKErrorDomain {
-                        return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
-                    }
-                    return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
-                case .invalidProductId(let productId):
-                    return alertWithTitle("Purchase failed", message: "\(productId) is not a valid product identifier")
-                case .paymentNotAllowed:
-                    return alertWithTitle("Payments not enabled", message: "You are not allowed to make payments")
+            switch error.code {
+            case .unknown: return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
+            case .clientInvalid: // client is not allowed to issue the request, etc.
+                return alertWithTitle("Purchase failed", message: "Not allowed to make the payment")
+            case .paymentCancelled: // user cancelled the request, etc.
+                return nil
+            case .paymentInvalid: // purchase identifier was invalid, etc.
+                return alertWithTitle("Purchase failed", message: "The purchase identifier was invalid")
+            case .paymentNotAllowed: // this device is not allowed to make the payment
+                return alertWithTitle("Purchase failed", message: "The device is not allowed to make the payment")
+            case .storeProductNotAvailable: // Product is not available in the current storefront
+                return alertWithTitle("Purchase failed", message: "The product is not available in the current storefront")
+            case .cloudServicePermissionDenied: // user has not allowed access to cloud service information
+                return alertWithTitle("Purchase failed", message: "Access to cloud service information is not allowed")
+            case .cloudServiceNetworkConnectionFailed: // the device could not connect to the nework
+                return alertWithTitle("Purchase failed", message: "Could not connect to the network")
             }
         }
     }

+ 16 - 15
SwiftyStoreKit-macOS-Demo/ViewController.swift

@@ -84,7 +84,9 @@ class ViewController: NSViewController {
                 }
             }
 
-            self.showAlert(self.alertForPurchaseResult(result))
+            if let errorAlert = self.alertForPurchaseResult(result) {
+                self.showAlert(errorAlert)
+            }
         }
     }
 
@@ -200,24 +202,23 @@ extension ViewController {
         }
     }
     
-    func alertForPurchaseResult(_ result: PurchaseResult) -> NSAlert {
-
+    func alertForPurchaseResult(_ result: PurchaseResult) -> NSAlert? {
         switch result {
-        case .success(let productId):
-            print("Purchase Success: \(productId)")
+        case .success(let product):
+            print("Purchase Success: \(product.productId)")
             return alertWithTitle("Thank You", message: "Purchase completed")
         case .error(let error):
             print("Purchase Failed: \(error)")
-            switch error {
-            case .failed(let error):
-                if (error as NSError).domain == SKErrorDomain {
-                    return alertWithTitle("Purchase failed", message: "Please check your Internet connection or try again later")
-                }
-                return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
-            case .invalidProductId(let productId):
-                return alertWithTitle("Purchase failed", message: "\(productId) is not a valid product identifier")
-            case .paymentNotAllowed:
-                return alertWithTitle("Payments not enabled", message: "You are not allowed to make payments")
+            switch error.code {
+            case .unknown: return alertWithTitle("Purchase failed", message: "Unknown error. Please contact support")
+            case .clientInvalid: // client is not allowed to issue the request, etc.
+                return alertWithTitle("Purchase failed", message: "Not allowed to make the payment")
+            case .paymentCancelled: // user cancelled the request, etc.
+                return nil
+            case .paymentInvalid: // purchase identifier was invalid, etc.
+                return alertWithTitle("Purchase failed", message: "The purchase identifier was invalid")
+            case .paymentNotAllowed: // this device is not allowed to make the payment
+                return alertWithTitle("Purchase failed", message: "The device is not allowed to make the payment")
             }
         }
     }

+ 1 - 1
SwiftyStoreKit.podspec

@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name         = 'SwiftyStoreKit'
-  s.version      = '0.7.1'
+  s.version      = '0.8.0'
   s.summary      = 'Lightweight In App Purchases Swift framework for iOS 8.0+, tvOS 9.0+ and OSX 10.10+'
   s.license      = 'MIT'
   s.homepage     = 'https://github.com/bizz84/SwiftyStoreKit'

+ 1 - 1
SwiftyStoreKit/PaymentQueueController.swift

@@ -38,7 +38,7 @@ protocol TransactionController {
 public enum TransactionResult {
     case purchased(product: Product)
     case restored(product: Product)
-    case failed(error: Error)
+    case failed(error: SKError)
 }
 
 public protocol PaymentQueue: class {

+ 8 - 3
SwiftyStoreKit/PaymentsController.swift

@@ -87,9 +87,7 @@ class PaymentsController: TransactionController {
         }
         if transactionState == .failed {
 
-            let message = "Transaction failed for product ID: \(transactionProductIdentifier)"
-            let altError = NSError(domain: SKErrorDomain, code: 0, userInfo: [ NSLocalizedDescriptionKey: message ])
-            payment.callback(.failed(error: transaction.error ?? altError))
+            payment.callback(.failed(error: transactionError(for: transaction.error as NSError?)))
             
             paymentQueue.finishTransaction(transaction)
             payments.remove(at: paymentIndex)
@@ -102,6 +100,13 @@ class PaymentsController: TransactionController {
         return false
     }
     
+    func transactionError(for error: NSError?) -> SKError {
+        let message = "Unknown error"
+        let altError = NSError(domain: SKErrorDomain, code: SKError.unknown.rawValue, userInfo: [ NSLocalizedDescriptionKey: message ])
+        let nsError = error ?? altError
+        return SKError(_nsError: nsError)
+    }
+        
     func processTransactions(_ transactions: [SKPaymentTransaction], on paymentQueue: PaymentQueue) -> [SKPaymentTransaction] {
         
         return transactions.filter { !processTransaction($0, on: paymentQueue) }

+ 1 - 1
SwiftyStoreKit/RestorePurchasesController.swift

@@ -84,7 +84,7 @@ class RestorePurchasesController: TransactionController {
         guard let restorePurchases = restorePurchases else {
             return
         }
-        restoredProducts.append(.failed(error: error))
+        restoredProducts.append(.failed(error: SKError(_nsError: error as NSError)))
         restorePurchases.callback(restoredProducts)
         
         // Reset state after error received

+ 2 - 9
SwiftyStoreKit/SwiftyStoreKit+Types.swift

@@ -54,23 +54,16 @@ public struct RetrieveResults {
     public let error: Error?
 }
 
-// Purchase error types
-public enum PurchaseError {
-    case failed(error: Error)
-    case invalidProductId(productId: String)
-    case paymentNotAllowed
-}
-
 // Purchase result
 public enum PurchaseResult {
     case success(product: Product)
-    case error(error: PurchaseError)
+    case error(error: SKError)
 }
 
 // Restore purchase results
 public struct RestoreResults {
     public let restoredProducts: [Product]
-    public let restoreFailedProducts: [(Swift.Error, String?)]
+    public let restoreFailedProducts: [(SKError, String?)]
 }
 
 // MARK: Receipt verification

+ 14 - 14
SwiftyStoreKit/SwiftyStoreKit.swift

@@ -32,11 +32,6 @@ public class SwiftyStoreKit {
     
     private var receiptRefreshRequest: InAppReceiptRefreshRequest?
     
-    private enum InternalErrorCode: Int {
-        case restoredPurchaseWhenPurchasing = 0
-        case purchasedWhenRestoringPurchase = 1
-    }
-    
     init(productsInfoController: ProductsInfoController = ProductsInfoController(),
          paymentQueueController: PaymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default())) {
         
@@ -61,10 +56,12 @@ public class SwiftyStoreKit {
                     self.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
                 }
                 else if let error = result.error {
-                    completion(.error(error: .failed(error: error)))
+                    completion(.error(error: SKError(_nsError: error as NSError)))
                 }
                 else if let invalidProductId = result.invalidProductIDs.first {
-                    completion(.error(error: .invalidProductId(productId: invalidProductId)))
+                    let userInfo = [ NSLocalizedDescriptionKey: "Invalid product id: \(invalidProductId)" ]
+                    let error = NSError(domain: SKErrorDomain, code: SKError.paymentInvalid.rawValue, userInfo: userInfo)
+                    completion(.error(error: SKError(_nsError: error)))
                 }
             }
         }
@@ -111,7 +108,8 @@ public class SwiftyStoreKit {
     // MARK: private methods
     private func purchase(product: SKProduct, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> ()) {
         guard SwiftyStoreKit.canMakePayments else {
-            completion(.error(error: .paymentNotAllowed))
+            let error = NSError(domain: SKErrorDomain, code: SKError.paymentNotAllowed.rawValue, userInfo: nil)
+            completion(.error(error: SKError(_nsError: error)))
             return
         }
         
@@ -126,19 +124,20 @@ public class SwiftyStoreKit {
         case .purchased(let product):
             return .success(product: product)
         case .failed(let error):
-            return .error(error: .failed(error: error))
+            return .error(error: error)
         case .restored(let product):
-            return .error(error: .failed(error: storeInternalError(code: InternalErrorCode.restoredPurchaseWhenPurchasing.rawValue, description: "Cannot restore product \(product.productId) from purchase path")))
+            return .error(error: storeInternalError(description: "Cannot restore product \(product.productId) from purchase path"))
         }
     }
     
     private func processRestoreResults(_ results: [TransactionResult]) -> RestoreResults {
         var restoredProducts: [Product] = []
-        var restoreFailedProducts: [(Swift.Error, String?)] = []
+        var restoreFailedProducts: [(SKError, String?)] = []
         for result in results {
             switch result {
             case .purchased(let product):
-                restoreFailedProducts.append((storeInternalError(code: InternalErrorCode.purchasedWhenRestoringPurchase.rawValue, description: "Cannot purchase product \(product.productId) from restore purchases path"), product.productId))
+                let error = storeInternalError(description: "Cannot purchase product \(product.productId) from restore purchases path")
+                restoreFailedProducts.append((error, product.productId))
             case .failed(let error):
                 restoreFailedProducts.append((error, nil))
             case .restored(let product):
@@ -148,8 +147,9 @@ public class SwiftyStoreKit {
         return RestoreResults(restoredProducts: restoredProducts, restoreFailedProducts: restoreFailedProducts)
     }
     
-    private func storeInternalError(code: Int = 0, description: String = "") -> NSError {
-        return NSError(domain: "SwiftyStoreKit", code: code, userInfo: [ NSLocalizedDescriptionKey: description ])
+    private func storeInternalError(code: SKError.Code = SKError.unknown, description: String = "") -> SKError {
+        let error = NSError(domain: SKErrorDomain, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey: description ])
+        return SKError(_nsError: error)
     }
 }