App Store Subscriptions and Family Sharing

A toot by my friend Casey brought back some frustrating memories about expired subscriptions that haven’t expired (yes, really). This blog post will hopefully help you avoid having these same recollections.

It all begins when a customer contacts you with a screenshot that looks something like this:

Your code and the App Store don’t agree about when a subscription expired. The cause of this is Apple’s StoreKit sample code. It’s likely that you have some code similar to line 246 of Store.swift:

subscriptionGroupStatus = try? await subscriptions.first?.subscription?.status.first?.state

That code will work fine until you encounter a customer that has Family Sharing enabled, as most do. The issue is that the Product.SubscriptionInfo can contain multiple items, and the code above only checks the first one.

How can that happen? With Family Sharing, the people who are using the subscription act independently: one may subscribe for a year and then cancel. Then another could subscribe at a later date for only a month. You have to check all of the subscriptions, not just the first one. Something like this:

if let statuses = try? await subscriptions.first?.subscription?.status {
let checkStatus = statuses.first { $0.state == .subscribed || $0.state == .inGracePeriod }
...
}

The documentation and sample code doesn’t say it, so I will: Apple’s StoreKit sample doesn’t support Family Sharing.

If you’re looking for code from Apple that does support Family Sharing, you can find it buried in one of the WWDC demo apps. Obviously.

What’s most frustrating about this situation is that you know it exists if you’ve read the documentation:

The array can have more than one subscription status if your subscription supports Family Sharing. Provide the customer with service for the subscription based on the highest level of service where the state is subscribed.

Which makes no sense until you’ve read the paragraphs above.

Actually, I was wrong. The most frustrating thing about this situation is that it’s essentially untestable. You can’t reproduce the problem, even after a customer lets you know they’re having issues and you’ve read this blog post. That’s because there is:

No way to test this in Xcode (even if it’s turned on in .storekit configuration).

No way to test this in TestFlight (fake purchases don’t use Family Sharing).

No reasonable way to test this in production (red flags will be raised with refunding and changing purchases repeatedly while testing with real Apple IDs).

The StoreKit test harness in Xcode has been a godsend, but in this case it’s just not up to the task. And the result is lots of frustrated developers who are testing code in production on a customer’s device.

Apple folks: you can learn more in FB13212468. It’s been closed as “Investigation complete” — maybe you should ask Casey if he agrees with that resolution.