Browse Source

Clearer separation between SwiftyStoreKit class and instance methods / variables. Added internal initialiser and methods to enable testability

Andrea Bizzotto 8 years ago
parent
commit
03d563a140
1 changed files with 141 additions and 96 deletions
  1. 141 96
      SwiftyStoreKit/SwiftyStoreKit.swift

+ 141 - 96
SwiftyStoreKit/SwiftyStoreKit.swift

@@ -26,9 +26,9 @@ import StoreKit
 
 public class SwiftyStoreKit {
 
-    private let productsInfoController = ProductsInfoController()
+    private let productsInfoController: ProductsInfoController
 
-    private let paymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default())
+    private let paymentQueueController: PaymentQueueController
     
     private var receiptRefreshRequest: InAppReceiptRefreshRequest?
     
@@ -36,36 +36,29 @@ public class SwiftyStoreKit {
         case restoredPurchaseWhenPurchasing = 0
         case purchasedWhenRestoringPurchase = 1
     }
-
-    // MARK: Singleton
-    private static let sharedInstance = SwiftyStoreKit()
     
-    public class var canMakePayments: Bool {
-        return SKPaymentQueue.canMakePayments()
+    init(productsInfoController: ProductsInfoController = ProductsInfoController(),
+         paymentQueueController: PaymentQueueController = PaymentQueueController(paymentQueue: SKPaymentQueue.default())) {
+        
+        self.productsInfoController = productsInfoController
+        self.paymentQueueController = paymentQueueController
     }
 
-    // MARK: Public methods - Purchases
-    public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
-        
-        return sharedInstance.productsInfoController.retrieveProductsInfo(productIds, completion: completion)
+    // MARK: Internal methods
+    
+    func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
+        return productsInfoController.retrieveProductsInfo(productIds, completion: completion)
     }
     
-    /**
-     *  Purchase a product
-     *  - Parameter productId: productId as specified in iTunes Connect
-     *  - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately)
-     *  - Parameter applicationUsername: an opaque identifier for the user’s account on your system
-     *  - Parameter completion: handler for result
-     */
-    public class func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
+    func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
         
-        if let product = sharedInstance.productsInfoController.products[productId] {
-            sharedInstance.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
+        if let product = productsInfoController.products[productId] {
+            purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
         }
         else {
             retrieveProductsInfo(Set([productId])) { result -> () in
                 if let product = result.retrievedProducts.first {
-                    sharedInstance.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
+                    self.purchase(product: product, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
                 }
                 else if let error = result.error {
                     completion(.error(error: .failed(error: error)))
@@ -77,90 +70,30 @@ public class SwiftyStoreKit {
         }
     }
     
-    public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
-
-        sharedInstance.paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in
+    func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
         
-            let results = sharedInstance.processRestoreResults(results)
+        paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in
+            
+            let results = self.processRestoreResults(results)
             completion(results)
         })
     }
     
-    public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
+    func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
         
-        sharedInstance.paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
-    }
-    
-
-    public class func finishTransaction(_ transaction: PaymentTransaction) {
-     
-        sharedInstance.paymentQueueController.finishTransaction(transaction)
-    }
-
-    // MARK: Public methods - Receipt verification
-
-    /**
-     * Return receipt data from the application bundle. This is read from Bundle.main.appStoreReceiptURL
-     */
-    public static var localReceiptData: Data? {
-        return InAppReceipt.appStoreReceiptData
+        paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
     }
     
-    /**
-     *  Verify application receipt
-     *  - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
-     *  - Parameter session: the session used to make remote call.
-     *  - Parameter completion: handler for result
-     */
-    public class func verifyReceipt(
-		using validator: ReceiptValidator,
-        password: String? = nil,
-        completion:@escaping (VerifyReceiptResult) -> ()) {
-
-		InAppReceipt.verify(using: validator, password: password) { result in
-         
-            DispatchQueue.main.async {
-                completion(result)
-            }
-        }
-    }
-  
-    /**
-     *  Verify the purchase of a Consumable or NonConsumable product in a receipt
-     *  - Parameter productId: the product id of the purchase to verify
-     *  - Parameter inReceipt: the receipt to use for looking up the purchase
-     *  - return: either NotPurchased or Purchased
-     */
-    public class func verifyPurchase(
-        productId: String,
-        inReceipt receipt: ReceiptInfo
-    ) -> VerifyPurchaseResult {
-        return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt)
-    }
-  
-    /**
-     *  Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value.
-     *  - Parameter productId: the product id of the purchase to verify
-     *  - Parameter inReceipt: the receipt to use for looking up the subscription
-     *  - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification
-     *  - Parameter validDuration: the duration of the subscription. Only required for non-renewable subscription.
-     *  - return: either NotPurchased or Purchased / Expired with the expiry date found in the receipt
-     */
-    public class func verifySubscription(
-        productId: String,
-        inReceipt receipt: ReceiptInfo,
-        validUntil date: Date = Date(),
-        validDuration duration: TimeInterval? = nil
-    ) -> VerifySubscriptionResult {
-        return InAppReceipt.verifySubscription(productId: productId, inReceipt: receipt, validUntil: date, validDuration: duration)
+    func finishTransaction(_ transaction: PaymentTransaction) {
+        
+        paymentQueueController.finishTransaction(transaction)
     }
 
-    // After verifying receive and have `ReceiptError.NoReceiptData`, refresh receipt using this method
-    public class func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
-        sharedInstance.receiptRefreshRequest = InAppReceiptRefreshRequest.refresh(receiptProperties) { result in
-
-            sharedInstance.receiptRefreshRequest = nil
-
+    func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
+        receiptRefreshRequest = InAppReceiptRefreshRequest.refresh(receiptProperties) { result in
+            
+            self.receiptRefreshRequest = nil
+            
             switch result {
             case .success:
                 if let appStoreReceiptData = InAppReceipt.appStoreReceiptData {
@@ -175,6 +108,7 @@ public class SwiftyStoreKit {
         }
     }
 
+    
     // MARK: private methods
     private func purchase(product: SKProduct, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> ()) {
         guard SwiftyStoreKit.canMakePayments else {
@@ -219,3 +153,114 @@ public class SwiftyStoreKit {
         return NSError(domain: "SwiftyStoreKit", code: code, userInfo: [ NSLocalizedDescriptionKey: description ])
     }
 }
+
+extension SwiftyStoreKit {
+
+    // MARK: Singleton
+    private static let sharedInstance = SwiftyStoreKit()
+    
+    // MARK: Public methods - Purchases
+    public class var canMakePayments: Bool {
+        return SKPaymentQueue.canMakePayments()
+    }
+
+    public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
+        
+        return sharedInstance.retrieveProductsInfo(productIds, completion: completion)
+    }
+    
+    /**
+     *  Purchase a product
+     *  - Parameter productId: productId as specified in iTunes Connect
+     *  - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately)
+     *  - Parameter applicationUsername: an opaque identifier for the user’s account on your system
+     *  - Parameter completion: handler for result
+     */
+    public class func purchaseProduct(_ productId: String, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> ()) {
+        
+        sharedInstance.purchaseProduct(productId, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
+    }
+    
+    public class func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> ()) {
+        
+        sharedInstance.restorePurchases(atomically: atomically, applicationUsername: applicationUsername, completion: completion)
+    }
+    
+    public class func completeTransactions(atomically: Bool = true, completion: @escaping ([Product]) -> ()) {
+        
+        sharedInstance.completeTransactions(atomically: atomically, completion: completion)
+    }
+    
+    
+    public class func finishTransaction(_ transaction: PaymentTransaction) {
+        
+        sharedInstance.finishTransaction(transaction)
+    }
+
+    // After verifying receive and have `ReceiptError.NoReceiptData`, refresh receipt using this method
+    public class func refreshReceipt(_ receiptProperties: [String : Any]? = nil, completion: @escaping (RefreshReceiptResult) -> ()) {
+        
+        sharedInstance.refreshReceipt(receiptProperties, completion: completion)
+    }
+}
+
+extension SwiftyStoreKit {
+ 
+    // MARK: Public methods - Receipt verification
+    
+    /**
+     * Return receipt data from the application bundle. This is read from Bundle.main.appStoreReceiptURL
+     */
+    public static var localReceiptData: Data? {
+        return InAppReceipt.appStoreReceiptData
+    }
+    
+    /**
+     *  Verify application receipt
+     *  - Parameter password: Only used for receipts that contain auto-renewable subscriptions. Your app’s shared secret (a hexadecimal string).
+     *  - Parameter session: the session used to make remote call.
+     *  - Parameter completion: handler for result
+     */
+    public class func verifyReceipt(
+        using validator: ReceiptValidator,
+        password: String? = nil,
+        completion:@escaping (VerifyReceiptResult) -> ()) {
+        
+        InAppReceipt.verify(using: validator, password: password) { result in
+            
+            DispatchQueue.main.async {
+                completion(result)
+            }
+        }
+    }
+    
+    /**
+     *  Verify the purchase of a Consumable or NonConsumable product in a receipt
+     *  - Parameter productId: the product id of the purchase to verify
+     *  - Parameter inReceipt: the receipt to use for looking up the purchase
+     *  - return: either NotPurchased or Purchased
+     */
+    public class func verifyPurchase(
+        productId: String,
+        inReceipt receipt: ReceiptInfo
+        ) -> VerifyPurchaseResult {
+        return InAppReceipt.verifyPurchase(productId: productId, inReceipt: receipt)
+    }
+    
+    /**
+     *  Verify the purchase of a subscription (auto-renewable, free or non-renewing) in a receipt. This method extracts all transactions mathing the given productId and sorts them by date in descending order, then compares the first transaction expiry date against the validUntil value.
+     *  - Parameter productId: the product id of the purchase to verify
+     *  - Parameter inReceipt: the receipt to use for looking up the subscription
+     *  - Parameter validUntil: date to check against the expiry date of the subscription. If nil, no verification
+     *  - Parameter validDuration: the duration of the subscription. Only required for non-renewable subscription.
+     *  - return: either NotPurchased or Purchased / Expired with the expiry date found in the receipt
+     */
+    public class func verifySubscription(
+        productId: String,
+        inReceipt receipt: ReceiptInfo,
+        validUntil date: Date = Date(),
+        validDuration duration: TimeInterval? = nil
+        ) -> VerifySubscriptionResult {
+        return InAppReceipt.verifySubscription(productId: productId, inReceipt: receipt, validUntil: date, validDuration: duration)
+    }
+}