浏览代码

Allow PaymentController to enqueue multiple payments with same product identifier. Added tests to check correct behaviour.

Andrea Bizzotto 8 年之前
父节点
当前提交
bc50b3a231

+ 1 - 6
SwiftyStoreKit/PaymentQueueController.swift

@@ -85,16 +85,11 @@ public class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
     
     public func startPayment(_ payment: Payment) {
         
-        if paymentsController.hasPayment(payment) {
-            // return .inProgress
-            return
-        }
-        
         let skPayment = SKMutablePayment(product: payment.product)
         skPayment.applicationUsername = payment.applicationUsername
         paymentQueue.add(skPayment)
         
-        paymentsController.insert(payment)
+        paymentsController.append(payment)
     }
     
     public func restorePurchases(_ restorePurchases: RestorePurchases) {

+ 11 - 9
SwiftyStoreKit/PaymentsController.swift

@@ -42,35 +42,37 @@ public struct Payment: Hashable {
 
 public class PaymentsController: TransactionController {
     
-    private var payments: Set<Payment> = []
+    private var payments: [Payment] = []
     
     public init() { }
     
-    private func findPayment(withProductIdentifier identifier: String) -> Payment? {
+    private func findPaymentIndex(withProductIdentifier identifier: String) -> Int? {
         for payment in payments {
             if payment.product.productIdentifier == identifier {
-                return payment
+                return payments.index(of: payment)
             }
         }
         return nil
     }
     
     public func hasPayment(_ payment: Payment) -> Bool {
-        return findPayment(withProductIdentifier: payment.product.productIdentifier) != nil
+        return findPaymentIndex(withProductIdentifier: payment.product.productIdentifier) != nil
     }
     
-    public func insert(_ payment: Payment) {
-        payments.insert(payment)
+    public func append(_ payment: Payment) {
+        payments.append(payment)
     }
     
     public func processTransaction(_ transaction: SKPaymentTransaction, on paymentQueue: PaymentQueue) -> Bool {
         
         let transactionProductIdentifier = transaction.payment.productIdentifier
         
-        guard let payment = findPayment(withProductIdentifier: transactionProductIdentifier) else {
+        guard let paymentIndex = findPaymentIndex(withProductIdentifier: transactionProductIdentifier) else {
 
             return false
         }
+        let payment = payments[paymentIndex]
+        
         let transactionState = transaction.transactionState
         
         if transactionState == .purchased {
@@ -82,7 +84,7 @@ public class PaymentsController: TransactionController {
             if payment.atomically {
                 paymentQueue.finishTransaction(transaction)
             }
-            payments.remove(payment)
+            payments.remove(at: paymentIndex)
             return true
         }
         if transactionState == .failed {
@@ -92,7 +94,7 @@ public class PaymentsController: TransactionController {
             payment.callback(.failed(error: transaction.error ?? altError))
             
             paymentQueue.finishTransaction(transaction)
-            payments.remove(payment)
+            payments.remove(at: paymentIndex)
             return true
         }
         

+ 99 - 7
SwiftyStoreKitTests/PaymentsControllerTests.swift

@@ -32,12 +32,12 @@ class PaymentsControllerTests: XCTestCase {
      
         let payment = makeTestPayment(productIdentifier: "com.SwiftyStoreKit.product1") { result in }
 
-        let paymentsController = makePaymentsController(insertPayment: payment)
+        let paymentsController = makePaymentsController(appendPayments: [payment])
         
         XCTAssertTrue(paymentsController.hasPayment(payment))
     }
     
-    func testProcessTransaction_when_transactionStatePurchased_then_removesPayment_finishesTransaction_callsCallback() {
+    func testProcessTransaction_when_onePayment_transactionStatePurchased_then_removesPayment_finishesTransaction_callsCallback() {
         
         let productIdentifier = "com.SwiftyStoreKit.product1"
         let testProduct = TestProduct(productIdentifier: productIdentifier)
@@ -54,7 +54,7 @@ class PaymentsControllerTests: XCTestCase {
             }
         }
         
-        let paymentsController = makePaymentsController(insertPayment: payment)
+        let paymentsController = makePaymentsController(appendPayments: [payment])
         
         let transaction = TestPaymentTransaction(payment: SKPayment(product: testProduct), transactionState: .purchased)
         
@@ -71,7 +71,7 @@ class PaymentsControllerTests: XCTestCase {
         XCTAssertEqual(spy.finishTransactionCalledCount, 1)
     }
     
-    func testProcessTransaction_when_transactionStateFailed_then_removesPayment_finishesTransaction_callsCallback() {
+    func testProcessTransaction_when_onePayment_transactionStateFailed_then_removesPayment_finishesTransaction_callsCallback() {
         
         let productIdentifier = "com.SwiftyStoreKit.product1"
         let testProduct = TestProduct(productIdentifier: productIdentifier)
@@ -88,7 +88,7 @@ class PaymentsControllerTests: XCTestCase {
             }
         }
         
-        let paymentsController = makePaymentsController(insertPayment: payment)
+        let paymentsController = makePaymentsController(appendPayments: [payment])
         
         let transaction = TestPaymentTransaction(payment: SKPayment(product: testProduct), transactionState: .failed)
         
@@ -105,11 +105,103 @@ class PaymentsControllerTests: XCTestCase {
         XCTAssertEqual(spy.finishTransactionCalledCount, 1)
     }
     
-    func makePaymentsController(insertPayment payment: Payment) -> PaymentsController {
+    func testProcessTransaction_when_twoPaymentsSameId_firstTransactionStatePurchased_secondTransactionStateFailed_then_removesPayments_finishesTransactions_callsCallbacks() {
+
+        let productIdentifier = "com.SwiftyStoreKit.product1"
+        let testProduct1 = TestProduct(productIdentifier: productIdentifier)
+        
+        var callback1Called = false
+        let payment1 = makeTestPayment(product: testProduct1) { result in
+            
+            callback1Called = true
+            if case .purchased(let product) = result {
+                XCTAssertEqual(product.productId, productIdentifier)
+            }
+            else {
+                XCTFail("expected purchased callback with product id")
+            }
+        }
+
+        let testProduct2 = TestProduct(productIdentifier: productIdentifier)
+
+        var callback2Called = false
+        let payment2 = makeTestPayment(product: testProduct2) { result in
+            callback2Called = true
+            if case .failed(_) = result {
+                
+            }
+            else {
+                XCTFail("expected failed callback with error")
+            }
+        }
+
+        let paymentsController = makePaymentsController(appendPayments: [payment1, payment2])
+        
+        let transaction1 = TestPaymentTransaction(payment: SKPayment(product: testProduct1), transactionState: .purchased)
+        let transaction2 = TestPaymentTransaction(payment: SKPayment(product: testProduct2), transactionState: .failed)
+        
+        let spy = PaymentQueueSpy()
+        
+        let remainingTransactions = paymentsController.processTransactions([transaction1, transaction2], on: spy)
+        
+        XCTAssertEqual(remainingTransactions.count, 0)
+        
+        XCTAssertFalse(paymentsController.hasPayment(payment1))
+        XCTAssertFalse(paymentsController.hasPayment(payment2))
+        
+        XCTAssertTrue(callback1Called)
+        XCTAssertTrue(callback2Called)
+        
+        XCTAssertEqual(spy.finishTransactionCalledCount, 2)
+    }
+
+    func testProcessTransaction_when_twoPaymentsSameId_firstPayment_transactionStatePurchased_then_removesFirstPayment_finishesTransaction_callsCallback() {
+        
+        let productIdentifier = "com.SwiftyStoreKit.product1"
+        let testProduct1 = TestProduct(productIdentifier: productIdentifier)
+        
+        var callback1Called = false
+        let payment1 = makeTestPayment(product: testProduct1) { result in
+            
+            callback1Called = true
+            if case .purchased(let product) = result {
+                XCTAssertEqual(product.productId, productIdentifier)
+            }
+            else {
+                XCTFail("expected purchased callback with product id")
+            }
+        }
+        
+        let testProduct2 = TestProduct(productIdentifier: productIdentifier)
+        let payment2 = makeTestPayment(product: testProduct2) { result in
+            
+            XCTFail("unexpected callback for second payment")
+        }
+        
+        let paymentsController = makePaymentsController(appendPayments: [payment1, payment2])
+        
+        let transaction1 = TestPaymentTransaction(payment: SKPayment(product: testProduct1), transactionState: .purchased)
+        
+        let spy = PaymentQueueSpy()
+        
+        let remainingTransactions = paymentsController.processTransactions([transaction1], on: spy)
+        
+        XCTAssertEqual(remainingTransactions.count, 0)
+        
+        // First one removed, but second one with same identifier still there
+        XCTAssertTrue(paymentsController.hasPayment(payment2))
+        
+        XCTAssertTrue(callback1Called)
+        
+        XCTAssertEqual(spy.finishTransactionCalledCount, 1)
+    }
+
+    
+    func makePaymentsController(appendPayments payments: [Payment]) -> PaymentsController {
         
         let paymentsController = PaymentsController()
         
-        paymentsController.insert(payment)
+        payments.forEach { paymentsController.append($0) }
         
         return paymentsController
     }