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

Feature/payment queue controller #131

Merged
merged 33 commits into from
Jan 29, 2017
Merged

Conversation

bizz84
Copy link
Owner

@bizz84 bizz84 commented Jan 21, 2017

This is a major refactor of the purchase flows in SwiftyStoreKit. Public facing API is unchanged.

Before: Payments / restore purchases / complete transactions operations were handled by specific requests that would register as observers of SKPaymentQueue, and deallocated as soon as the response was returned. This caused various problems in correctly dispatching the updated transactions to the correct requests.
After: Payments / restore purchases / complete transactions are all composed together inside a PaymentQueueController class which is the only observer of SKPaymentQueue.

The new functionality is extensively unit tested against the following set of criteria:

  • SKPaymentQueue is used to queue payments or restore purchases requests.
  • Payments are processed serially and in-order and require user interaction.
  • Restore purchases requests don't require user interaction and can jump ahead of the queue.
  • SKPaymentQueue rejects multiple restore purchases calls.
  • Having one payment queue observer for each request causes extra processing
  • Failed translations only ever belong to queued payment request.
  • restoreCompletedTransactionsFailedWithError is always called when a restore purchases request fails.
  • paymentQueueRestoreCompletedTransactionsFinished is always called following 0 or more update transactions when a restore purchases request succeeds.
  • A complete transactions handler is require to catch any transactions that are updated when the app is not running.
  • Registering a complete transactions handler when the app launches ensures that any pending transactions can be cleared.
  • If a complete transactions handler is missing, pending transactions can be mis-attributed to any new incoming payments or restore purchases.

The order in which transaction updates are processed is:

  1. payments (transactionState: .purchased and .failed for matching product identifiers)
  2. restore purchases (transactionState: .restored, or restoreCompletedTransactionsFailedWithError, or paymentQueueRestoreCompletedTransactionsFinished)
  3. complete transactions (transactionState: .purchased, .failed, .restored, .deferred)

Any transactions where state == .purchasing are ignored.

Checklist

  • PaymentQueueController
  • PaymentQueueController unit tests
  • PaymentQueueController integration tests
  • PaymentController
  • PaymentController unit tests
  • RestorePurchasesController
  • RestorePurchasesController unit tests
  • CompeteTransactionsController
  • CompeteTransactionsController unit tests
  • Integrate PaymentQueueController into SwiftyStoreKit.swift
  • Remove old classes
  • ProductsInfoController
  • ProductsInfoController unit tests
  • Add SwiftyStoreKitTests to Travis CI
  • Review access levels for new classes (make internal rather than public where appropriate: this enables unit tests but restricts usage to existing API)
  • Clearly separate class and instance methods in SwiftyStoreKit
  • Update README with purchase flows documentation
  • SwiftyStoreKit unit tests (this could be achieved by creating an internal init() method with default parameters for SKPaymentQueue and other required dependencies)
  • Manual testing

Planned future work (will be done in separate PR)

  • Unit tests for receipt verification code

…eference and documentation about the order in which transactions are processed.
…t identifier. Added tests to check correct behaviour.
…transaction in an array, and call the callback in restoreCompletedTransactionsFailed or restoreCompletedTransactionsFinished
@bizz84 bizz84 changed the base branch from master to develop January 21, 2017 16:18
This was referenced Jan 21, 2017

private func allProductsMatching(_ productIds: Set<String>) -> Set<SKProduct>? {

var requestedProducts = Set<SKProduct>()
Copy link

@almostintuitive almostintuitive Jan 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function below can be replaced by:
return Set(productIds.flatMap { self.products[$0] })

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not precisely. The previous method returns nil if at least one product ID has not been fetched yet. Doing this with flatMap discards all nil values but still returns a non empty array for values that have been already fetched.
This could result in the a purchase request getting stuck because the required SKProduct is not queried.

I have found a way to make this work with flatMap, as follows:

    private func allProductsMatching(_ productIds: Set<String>) -> Set<SKProduct>? {
        
        return Set(productIds.flatMap { self.products[$0] })
    }
    func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> ()) {
        
        guard let products = allProductsMatching(productIds), products.count == productIds.count else {
            
            requestProducts(productIds, completion: completion)
            return
        }
        completion(RetrieveResults(retrievedProducts: products, invalidProductIDs: [], error: nil))
    }

This way, if allProductsMatching returns a set of size less than the set of product IDs, the products are requested again.

@bizz84 bizz84 merged commit ee4dfd0 into develop Jan 29, 2017
@bizz84 bizz84 deleted the feature/payment-queue-controller branch February 22, 2017 22:28
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.

None yet

2 participants