Browse Source

Add PaymentsController

Andrea Bizzotto 8 years ago
parent
commit
64c7325220

+ 8 - 0
SwiftyStoreKit.xcodeproj/project.pbxproj

@@ -35,6 +35,9 @@
 		65BB6CE91DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */; };
 		65BB6CEA1DDB018900218A0B /* SwiftyStoreKit+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */; };
 		65F70AC71E2ECBB300BF040D /* PaymentTransactionObserverFake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC61E2ECBB300BF040D /* PaymentTransactionObserverFake.swift */; };
+		65F70AC91E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
+		65F70ACA1E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
+		65F70ACB1E2EDC3700BF040D /* Payments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F70AC81E2EDC3700BF040D /* Payments.swift */; };
 		65F7DF711DCD4DF000835D30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F7DF681DCD4DF000835D30 /* AppDelegate.swift */; };
 		65F7DF721DCD4DF000835D30 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 65F7DF691DCD4DF000835D30 /* Assets.xcassets */; };
 		65F7DF731DCD4DF000835D30 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65F7DF6A1DCD4DF000835D30 /* LaunchScreen.storyboard */; };
@@ -136,6 +139,7 @@
 		658A084B1E2EC5960074A98F /* PaymentQueueSpy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentQueueSpy.swift; sourceTree = "<group>"; };
 		65BB6CE71DDB018900218A0B /* SwiftyStoreKit+Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftyStoreKit+Types.swift"; sourceTree = "<group>"; };
 		65F70AC61E2ECBB300BF040D /* PaymentTransactionObserverFake.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentTransactionObserverFake.swift; sourceTree = "<group>"; };
+		65F70AC81E2EDC3700BF040D /* Payments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Payments.swift; sourceTree = "<group>"; };
 		65F7DF681DCD4DF000835D30 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		65F7DF691DCD4DF000835D30 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		65F7DF6B1DCD4DF000835D30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
@@ -249,6 +253,7 @@
 				C40C680F1C29414C00B60B7E /* OS.swift */,
 				65F7DF931DCD536100835D30 /* Platforms */,
 				658A08361E2EC24E0074A98F /* PaymentQueueController.swift */,
+				65F70AC81E2EDC3700BF040D /* Payments.swift */,
 			);
 			path = SwiftyStoreKit;
 			sourceTree = "<group>";
@@ -564,6 +569,7 @@
 				54C0D5681CF7428400F90BCE /* SwiftyStoreKit.swift in Sources */,
 				54B069961CF744DC00BAFE38 /* OS.swift in Sources */,
 				54B069931CF742D300BAFE38 /* InAppReceiptRefreshRequest.swift in Sources */,
+				65F70ACB1E2EDC3700BF040D /* Payments.swift in Sources */,
 				658A08391E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722831DB8290B00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				54B069921CF742D100BAFE38 /* InAppReceipt.swift in Sources */,
@@ -592,6 +598,7 @@
 				6502F63A1B985C9E004E342D /* InAppProductPurchaseRequest.swift in Sources */,
 				6502F63B1B985CA1004E342D /* InAppProductQueryRequest.swift in Sources */,
 				C4083C571C2AB0A900295248 /* InAppReceiptRefreshRequest.swift in Sources */,
+				65F70AC91E2EDC3700BF040D /* Payments.swift in Sources */,
 				658A08371E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722811DB8282600C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4A7C7631C29B8D00053ED64 /* InAppReceipt.swift in Sources */,
@@ -620,6 +627,7 @@
 				C4D74BC31C24CEDC0071AD3E /* InAppProductPurchaseRequest.swift in Sources */,
 				C4D74BC41C24CEDC0071AD3E /* InAppProductQueryRequest.swift in Sources */,
 				C4F69A8A1C2E0D21009DD8BD /* InAppReceiptRefreshRequest.swift in Sources */,
+				65F70ACA1E2EDC3700BF040D /* Payments.swift in Sources */,
 				658A08381E2EC24E0074A98F /* PaymentQueueController.swift in Sources */,
 				653722821DB8290A00C8F944 /* SKProduct+LocalizedPrice.swift in Sources */,
 				C4083C551C2AADB500295248 /* InAppReceipt.swift in Sources */,

+ 41 - 15
SwiftyStoreKit/PaymentQueueController.swift

@@ -33,39 +33,31 @@ public protocol PaymentQueue: class {
     func add(_ payment: SKPayment)
     
     func restoreCompletedTransactions()
+    
+    func finishTransaction(_ transaction: SKPaymentTransaction)
 }
 
 extension SKPaymentQueue: PaymentQueue { }
 
 public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
 
-    public enum TransactionResult {
-        case purchased(product: Product)
-        case restored(product: Product)
-        case failed(error: Error)
-    }
-    
-    public struct Payment {
-        public let product: SKProduct
-        public let atomically: Bool
-        public let applicationUsername: String
-        public let callback: (TransactionResult) -> ()
-    }
-    
     public struct RestorePurchases {
         let atomically: Bool
         let callback: ([TransactionResult]) -> ()
     }
-
+    
+    private let paymentsController: PaymentsController
+    
     unowned let paymentQueue: PaymentQueue
 
     deinit {
         paymentQueue.remove(self)
     }
 
-    public init(paymentQueue: PaymentQueue = SKPaymentQueue.default()) {
+    public init(paymentQueue: PaymentQueue = SKPaymentQueue.default(), paymentsController: PaymentsController = PaymentsController()) {
      
         self.paymentQueue = paymentQueue
+        self.paymentsController = paymentsController
         super.init()
         paymentQueue.add(self)
     }
@@ -75,12 +67,39 @@ public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
         let skPayment = SKMutablePayment(product: payment.product)
         skPayment.applicationUsername = payment.applicationUsername
         paymentQueue.add(skPayment)
+        
+        paymentsController.insert(payment)
     }
     
     
     // MARK: SKPaymentTransactionObserver
     public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
 
+        for transaction in transactions {
+            
+            let transactionState = transaction.transactionState
+
+            switch transactionState {
+            case .purchased:
+                
+                let _ = paymentsController.processTransaction(transaction, paymentQueue: paymentQueue)
+                
+                break
+            case .failed:
+                break
+
+            case .restored:
+                break
+
+            case .purchasing:
+                // In progress: do nothing
+                break
+            case .deferred:
+                break
+            }
+
+        }
+        
     }
     
     public func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
@@ -100,3 +119,10 @@ public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
     }
 
 }
+
+/*
+ If more than one payment is queued for a given product Id,
+ only the first callback should be called to ensure the content is delivered only once
+ 
+ 
+ */

+ 67 - 0
SwiftyStoreKit/Payments.swift

@@ -0,0 +1,67 @@
+//
+//  Payments.swift
+//  SwiftyStoreKit
+//
+//  Created by Andrea Bizzotto on 17/01/2017.
+//  Copyright © 2017 musevisions. All rights reserved.
+//
+
+import Foundation
+import StoreKit
+
+public enum TransactionResult {
+    case purchased(product: Product)
+    case restored(product: Product)
+    case failed(error: Error)
+}
+
+public struct Payment: Hashable {
+    public let product: SKProduct
+    public let atomically: Bool
+    public let applicationUsername: String
+    public let callback: (TransactionResult) -> ()
+    
+    public var hashValue: Int {
+        return product.productIdentifier.hashValue
+    }
+    public static func ==(lhs: Payment, rhs: Payment) -> Bool {
+        return lhs.product.productIdentifier == rhs.product.productIdentifier
+    }
+}
+
+public class PaymentsController {
+    
+    private var payments: Set<Payment> = []
+    
+    private func findPayment(withProductIdentifier identifier: String) -> Payment? {
+        for payment in payments {
+            if payment.product.productIdentifier == identifier {
+                return payment
+            }
+        }
+        return nil
+    }
+    
+    public func insert(_ payment: Payment) {
+        payments.insert(payment)
+    }
+    
+    public func processTransaction(_ transaction: SKPaymentTransaction, paymentQueue: PaymentQueue) -> Bool {
+        
+        let transactionProductIdentifier = transaction.payment.productIdentifier
+        
+        if let payment = findPayment(withProductIdentifier: transactionProductIdentifier) {
+            
+            let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !payment.atomically)
+            
+            payment.callback(.purchased(product: product))
+            
+            if payment.atomically {
+                paymentQueue.finishTransaction(transaction)
+            }
+            payments.remove(payment)
+            return true
+        }
+        return false
+    }
+}

+ 3 - 3
SwiftyStoreKitTests/PaymentQueueControllerTests.swift

@@ -26,8 +26,8 @@ import XCTest
 import SwiftyStoreKit
 import StoreKit
 
-extension PaymentQueueController.Payment {
-    public init(product: SKProduct, atomically: Bool, applicationUsername: String, callback: @escaping (PaymentQueueController.TransactionResult) -> ()) {
+extension Payment {
+    public init(product: SKProduct, atomically: Bool, applicationUsername: String, callback: @escaping (TransactionResult) -> ()) {
         self.product = product
         self.atomically = atomically
         self.applicationUsername = applicationUsername
@@ -79,7 +79,7 @@ class PaymentQueueControllerTests: XCTestCase {
         let paymentQueueController = PaymentQueueController(paymentQueue: spy)
 
         let product = TestProduct(productIdentifier: "com.SwiftyStoreKit.product1")
-        let payment = PaymentQueueController.Payment(product: product, atomically: true, applicationUsername: "", callback: { result in })
+        let payment = Payment(product: product, atomically: true, applicationUsername: "", callback: { result in })
 
         paymentQueueController.startPayment(payment)
         

+ 3 - 0
SwiftyStoreKitTests/PaymentQueueSpy.swift

@@ -38,4 +38,7 @@ class PaymentQueueSpy: PaymentQueue {
         restoreCompletedTransactionCalledCount += 1
     }
 
+    func finishTransaction(_ transaction: SKPaymentTransaction) {
+        
+    }
 }