Browse Source

Merge pull request #322 from bizz84/always-finish-failed-transactions

completeTransactions should finish failed transactions if atomically: false
Andrea Bizzotto 7 years ago
parent
commit
4e9fc9cb00

+ 4 - 0
CHANGELOG.md

@@ -2,6 +2,10 @@
 
 All notable changes to this project will be documented in this file.
 
+## [0.11.3](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.11.3) `completeTransactions` should finish failed transactions if `atomically: false`
+
+* `completeTransactions` should finish failed transactions if `atomically: false` ([#322](https://github.com/bizz84/SwiftyStoreKit/pull/322), related issues: [#307](https://github.com/bizz84/SwiftyStoreKit/issues/307), [#288](https://github.com/bizz84/SwiftyStoreKit/issues/288))
+
 ## [0.11.2](https://github.com/bizz84/SwiftyStoreKit/releases/tag/0.11.2) Remove `SKProduct.localizedIntroductoryPrice`
 
 * Remove `localizedIntroductoryPrice` ([#320](https://github.com/bizz84/SwiftyStoreKit/pull/320), see [#319](https://github.com/bizz84/SwiftyStoreKit/issues/319), [#318](https://github.com/bizz84/SwiftyStoreKit/pull/318), [#315](https://github.com/bizz84/SwiftyStoreKit/pull/315))

+ 15 - 11
README.md

@@ -35,17 +35,21 @@ SwiftyStoreKit supports this by calling `completeTransactions()` when the app st
 
 ```swift
 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
-    SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
-        for purchase in purchases {
-            if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored {
-                if purchase.needsFinishTransaction {
-                    // Deliver content from server, then:
-                    SwiftyStoreKit.finishTransaction(purchase.transaction)
-                }
-                print("purchased: \(purchase)")
-            }
-        }
-    }
+	// see notes below for the meaning of Atomic / Non-Atomic
+	SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
+	    for purchase in purchases {
+	        switch purchase.transaction.transactionState {
+	        case .purchased, .restored:
+	            if purchase.needsFinishTransaction {
+	                // Deliver content from server, then:
+	                SwiftyStoreKit.finishTransaction(purchase.transaction)
+	            }
+	            // Unlock content
+	        case .failed, .purchasing, .deferred:
+	            break // do nothing
+	        }
+	    }
+	}
     return true
 }
 ```

+ 5 - 3
SwiftyStoreKit-iOS-Demo/AppDelegate.swift

@@ -42,13 +42,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
 
             for purchase in purchases {
-                if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored {
-
+                switch purchase.transaction.transactionState {
+                case .purchased, .restored:
                     if purchase.needsFinishTransaction {
                         // Deliver content from server, then:
                         SwiftyStoreKit.finishTransaction(purchase.transaction)
                     }
-                    print("purchased: \(purchase.productId)")
+                    print("\(purchase.transaction.transactionState.debugDescription): \(purchase.productId)")
+                case .failed, .purchasing, .deferred:
+                    break // do nothing
                 }
             }
         }

+ 5 - 3
SwiftyStoreKit-macOS-Demo/AppDelegate.swift

@@ -38,13 +38,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
         SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
 
             for purchase in purchases {
-                if purchase.transaction.transactionState == .purchased || purchase.transaction.transactionState == .restored {
-
+                switch purchase.transaction.transactionState {
+                case .purchased, .restored:
                     if purchase.needsFinishTransaction {
                         // Deliver content from server, then:
                         SwiftyStoreKit.finishTransaction(purchase.transaction)
                     }
-                    print("purchased: \(purchase.productId)")
+                    print("\(purchase.transaction.transactionState.debugDescription): \(purchase.productId)")
+                case .failed, .purchasing, .deferred:
+                    break // do nothing
                 }
             }
         }

+ 4 - 4
SwiftyStoreKit/CompleteTransactionsController.swift

@@ -55,13 +55,13 @@ class CompleteTransactionsController: TransactionController {
 
             if transactionState != .purchasing {
 
-                let purchase = Purchase(productId: transaction.payment.productIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !completeTransactions.atomically)
+                let willFinishTransaction = completeTransactions.atomically || transactionState == .failed
+                let purchase = Purchase(productId: transaction.payment.productIdentifier, quantity: transaction.payment.quantity, transaction: transaction, originalTransaction: transaction.original, needsFinishTransaction: !willFinishTransaction)
 
                 purchases.append(purchase)
 
-                print("Finishing transaction for payment \"\(transaction.payment.productIdentifier)\" with state: \(transactionState.debugDescription)")
-
-                if completeTransactions.atomically {
+                if willFinishTransaction {
+                    print("Finishing transaction for payment \"\(transaction.payment.productIdentifier)\" with state: \(transactionState.debugDescription)")
                     paymentQueue.finishTransaction(transaction)
                 }
             } else {

+ 63 - 5
SwiftyStoreKitTests/CompleteTransactionsControllerTests.swift

@@ -28,7 +28,7 @@ import StoreKit
 
 class CompleteTransactionsControllerTests: XCTestCase {
 
-    func testProcessTransactions_when_oneRestoredTransaction_then_finishesTransaction_callsCallback_noRemainingTransactions() {
+    func testProcessTransactions_when_atomically_oneRestoredTransaction_then_finishesTransaction_callsCallback_noRemainingTransactions() {
 
         let productIdentifier = "com.SwiftyStoreKit.product1"
         let testProduct = TestProduct(productIdentifier: productIdentifier)
@@ -56,7 +56,65 @@ class CompleteTransactionsControllerTests: XCTestCase {
         XCTAssertEqual(spy.finishTransactionCalledCount, 1)
     }
 
-    func testProcessTransactions_when_oneTransactionForEachState_then_finishesTransactions_callsCallback_onePurchasingTransactionRemaining() {
+    func testProcessTransactions_when_atomically_oneFailedTransaction_then_finishesTransaction_callsCallback_noRemainingTransactions_noNeedsFinishTransactions() {
+        
+        let productIdentifier = "com.SwiftyStoreKit.product1"
+        let testProduct = TestProduct(productIdentifier: productIdentifier)
+        
+        let transaction = TestPaymentTransaction(payment: SKPayment(product: testProduct), transactionState: .failed)
+        
+        var callbackCalled = false
+        let completeTransactions = CompleteTransactions(atomically: true) { purchases in
+            callbackCalled = true
+            XCTAssertEqual(purchases.count, 1)
+            let purchase = purchases.first!
+            XCTAssertFalse(purchase.needsFinishTransaction)
+            XCTAssertEqual(purchase.productId, productIdentifier)
+        }
+        
+        let completeTransactionsController = makeCompleteTransactionsController(completeTransactions: completeTransactions)
+        
+        let spy = PaymentQueueSpy()
+        
+        let remainingTransactions = completeTransactionsController.processTransactions([transaction], on: spy)
+        
+        XCTAssertEqual(remainingTransactions.count, 0)
+        
+        XCTAssertTrue(callbackCalled)
+        
+        XCTAssertEqual(spy.finishTransactionCalledCount, 1)
+    }
+
+    func testProcessTransactions_when_notAtomically_oneFailedTransaction_then_finishesTransaction_callsCallback_noRemainingTransactions_noNeedsFinishTransactions() {
+        
+        let productIdentifier = "com.SwiftyStoreKit.product1"
+        let testProduct = TestProduct(productIdentifier: productIdentifier)
+        
+        let transaction = TestPaymentTransaction(payment: SKPayment(product: testProduct), transactionState: .failed)
+        
+        var callbackCalled = false
+        let completeTransactions = CompleteTransactions(atomically: false) { purchases in
+            callbackCalled = true
+            XCTAssertEqual(purchases.count, 1)
+            let purchase = purchases.first!
+            XCTAssertFalse(purchase.needsFinishTransaction)
+            XCTAssertEqual(purchase.productId, productIdentifier)
+        }
+        
+        let completeTransactionsController = makeCompleteTransactionsController(completeTransactions: completeTransactions)
+        
+        let spy = PaymentQueueSpy()
+        
+        let remainingTransactions = completeTransactionsController.processTransactions([transaction], on: spy)
+        
+        XCTAssertEqual(remainingTransactions.count, 0)
+        
+        XCTAssertTrue(callbackCalled)
+        
+        XCTAssertEqual(spy.finishTransactionCalledCount, 1)
+    }
+
+    func testProcessTransactions_when_atomically_oneTransactionForEachState_then_finishesTransactions_callsCallback_onePurchasingTransactionRemaining() {
 
         let transactions = [
             makeTestPaymentTransaction(productIdentifier: "com.SwiftyStoreKit.product1", transactionState: .purchased),
@@ -89,7 +147,7 @@ class CompleteTransactionsControllerTests: XCTestCase {
         XCTAssertEqual(spy.finishTransactionCalledCount, 4)
     }
 
-    func testProcessTransactions_when_zeroTransactions_then_noFinishedTransactions_noCallback_noTransactionsRemaining() {
+    func testProcessTransactions_when_atomically_zeroTransactions_then_noFinishedTransactions_noCallback_noTransactionsRemaining() {
 
         let transactions: [TestPaymentTransaction] = []
 
@@ -108,7 +166,7 @@ class CompleteTransactionsControllerTests: XCTestCase {
         XCTAssertEqual(spy.finishTransactionCalledCount, 0)
     }
 
-    func testProcessTransactions_when_onePurchasingTransaction_then_noFinishedTransactions_noCallback_oneTransactionsRemaining() {
+    func testProcessTransactions_when_atomically_onePurchasingTransaction_then_noFinishedTransactions_noCallback_oneTransactionsRemaining() {
 
         let productIdentifier = "com.SwiftyStoreKit.product1"
         let testProduct = TestProduct(productIdentifier: productIdentifier)
@@ -129,7 +187,7 @@ class CompleteTransactionsControllerTests: XCTestCase {
 
         XCTAssertEqual(spy.finishTransactionCalledCount, 0)
     }
-
+    
     func makeTestPaymentTransaction(productIdentifier: String, transactionState: SKPaymentTransactionState) -> TestPaymentTransaction {
 
         let testProduct = TestProduct(productIdentifier: productIdentifier)