Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

completeTransactions should finish failed transactions if atomically: false #322

Merged
merged 6 commits into from
Dec 22, 2017

Conversation

bizz84
Copy link
Owner

@bizz84 bizz84 commented Dec 22, 2017

Context

Non-Atomic transactions are useful when the application needs to deliver content from the server before finishing a transaction.

When the application starts, it is best practice to call SwiftyStoreKit.completeTransactions. This method makes it possible to clear all transactions that may have completed while the app was not running.

Crucially, this applies to .purchased, .restored and .failed transactions. See Apple Docs for SKPaymentQueue:

// Asynchronous.  Remove a finished (i.e. failed or completed) transaction from the queue.  Attempting to finish a purchasing transaction will throw an exception.
@available(iOS 3.0, *)
open func finishTransaction(_ transaction: SKPaymentTransaction)

As of SwiftyStoreKit 0.11.2, calling SwiftyStoreKit.completeTransactions(atomically: false) does not call finishTransactions on .failed transactions.

This is not in line with purchaseProduct and restorePurchases, which both call finishTransactions on .failed transactions, regardless of the atomically flag.

Why this matters (side effects)

When transactions are not finished, they hang out in the payment queue indefinitely, and can cause problems with later purchases.

Consider this example:

  • User tries to purchase a product, but the transaction fails after the app is killed.
  • User opens the app again, which is configured with SwiftyStoreKit.completeTransactions(atomically: false)
  • The .failed transaction is not finished.
  • User tries to purchase the same product again, and the purchase fails immediately. This happens because the old (failed) transaction is still in the queue and SwiftyStoreKit thinks it matches the new purchase as the product ID is the same.
  • At this point, the old transaction is finished and the callback for the new purchase is called. The transaction for the new purchase is now in the queue (and may be handled by completeTransactions later).

How to fix this

Modify the implementation of completeTransactions to ensure failed transactions are always finished, even if atomically: false.

This ensures that all completed transactions are always caught and finished when the app launches, as long as SwiftyStoreKit.completeTransactions is called.

With this fix, it's also essential that purchase.needsFinishTransaction is always false when completeTransactions returns a failed transaction, regardless of the value of atomically.

For more clarity, the README will be updated to use a switch statement in the completion block of completeTransactions.

SwiftyStoreKit.completeTransactions(atomically: false) { 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)
            }
        case .failed, .purchasing, .deferred:
            break // do nothing
        }
    }
}

Note that with this fix, apps using the old sample code from the README should now work correctly:

SwiftyStoreKit.completeTransactions(atomically: false) { 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)")
        }
    }
}

@bizz84 bizz84 merged commit 4e9fc9c into develop Dec 22, 2017
@bizz84 bizz84 deleted the always-finish-failed-transactions branch December 26, 2017 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant