benjaminmayo / merchantkit

A modern In-App Purchases management framework for iOS.
MIT License
1.11k stars 72 forks source link

Move state(for: Product) functionality to purchase storage #38

Closed ekurutepe closed 5 years ago

ekurutepe commented 5 years ago

This is a relatively simple refactoring of state(for: Product).

The goal is to enable easy checking for keychain purchase storage from an app extension where a full-blown merchant is not a viable option since StoreKit API are unavailable in an extension.

I'm using this in my app extension as follows:

    guard let serviceName = Bundle.main.bundleIdentifier else {
      return
    }

    let keychain = KeychainPurchaseStorage(serviceName: serviceName)

    switch keychain.state(for: myProduct) {
    case .unknown, .notPurchased:
       // show purchase link
    case .isPurchased(_):
      // do the work
    }
benjaminmayo commented 5 years ago

Do you think this is materially better than letting the app logic handle it itself? For instance, the way I handle this in Daily Dictionary is storing a flag to the extension's container. I simply update this value in the merchant(_:, didChangeStatesFor:) delegate.

ekurutepe commented 5 years ago

I like this approach better for the consumers because it offloads it to the library and uses the same source of truth, the keychain, as opposed to duplicating state in the extension container. Less to go wrong…

benjaminmayo commented 5 years ago

I'm not sure the purchase storage should be viewed as the source of truth though. It's certainly the place where the Merchant stores its data, but that's technically a different concept. It's possible in the future that the Merchant wants to employ logic that resolves the PurchasedState using more than just the storage.

If you don't mind, I'd rather be cautious and hold off on this. All the purchase storage API is public so you can implement this in your own project as an extension if you really want to do it. But I think funnelling via the delegate is a better pattern to promote in general.

That is ultimately the pattern I've been running in my own apps and it works really well, plus it composes well if you have other properties that determine extension behaviour. You just call your update logic in the delegate, and you're rosy.