How do restore, Family Sharing and refunds work in StoreKit 2, and how should the app remove access?

28.3.2 · L3 · Interview

Predict first

What do the ownership and revocation raw values print, and does the restore/entitlement code compile?

import StoreKit

func restore() async -> Bool {
    do { try await AppStore.sync(); return true } catch { return false }
}
func ownsForever(_ productID: String) async -> Bool {
    for await result in Transaction.currentEntitlements {
        if case .verified(let t) = result, t.productID == productID, t.revocationDate == nil { return true }
    }
    return false
}
print(Transaction.OwnershipType.purchased.rawValue, Transaction.OwnershipType.familyShared.rawValue)
print(Transaction.RevocationReason.developerIssue.rawValue, Transaction.RevocationReason.other.rawValue)
print("owns lifetime in a plain command-line process:", await ownsForever("lifetime"))
  • PURCHASED FAMILY_SHARED, then 1 0, and false in a plain process
  • purchased familyShared, then 0 1, and true
  • it does not compile because AppStore.sync is iOS-only

Mental model. Restore is asking the library to look up your card on a new phone; a refund is the library taking a book back and striking it from your record, which you only learn when the record changes.

Restore. With StoreKit 2 entitlements sync automatically through the Apple ID, so a new device normally sees currentEntitlements without any action. App Review still expects a visible Restore Purchases control, which should call try await AppStore.sync() (it may ask the user to sign in, so only call it on an explicit tap, never at launch) and then re-derive entitlements. Family Sharing is opt-in per product in App Store Connect (and cannot be turned off again once enabled); a shared purchase appears with Transaction.ownershipType == .familyShared, and a family member who leaves, or an organiser who stops sharing, produces a revocation for that member. Show the owner of a purchase differently if it matters (for example do not offer "manage subscription" for someone else's plan).

Refunds. Users request refunds from Apple (reportaproblem), or in app with Transaction.beginRefundRequest(for:in:) on iOS, which presents Apple's sheet and returns whether the request was submitted (not whether it was granted); the outcome arrives later as a transaction update with revocationDate and a revocationReason (.developerIssue or .other) and as a server notification (REFUND; also CONSUMPTION_REQUEST, where Apple asks you for consumption data so it can decide). Remove access when you see a revoked transaction, reduce consumable balances if policy allows, and treat it as signed data like any other. The check compiles the restore and ownership code and prints the real raw values (PURCHASED, FAMILY_SHARED, 1 for developerIssue, 0 for other); in a bare command-line process there is no app, store or account, so ownership is false. beginRefundRequest needs a UI scene and was not compiled here.

Raw values are StoreKit's own; there is no store or account in a command-line process.

Common traps

  • Calling AppStore.sync() automatically at launch. It can show a sign-in prompt and is meant for an explicit restore tap.
  • Treating the refund request result as a refund. It only says the request was submitted; wait for the revocation.

They might follow up with

  • Can family members see my subscription? Only if the product is family shareable and the organiser has sharing on.
  • How should a consumable refund behave? Decide a policy (claw back, or accept the loss) and make it idempotent on the transaction id.

Related questions

  • 28.3.1 How do you compute what a user is entitled to, and which transactions are excluded from…
  • 28.1.4 Why start a Transaction.updates listener at launch, and what do finish() and idempotent delivery guarantee?
  • 28.4.3 How do App Store Server Notifications V2 behave, and how do you process them idempotently and out of order?
  • 17.4.2 How does StoreKit 2 work: products, purchases, transaction verification, entitlements, refunds, and what must…

← How do you compute what a user is entitled to, and which transactions…