Pick the environment for the question
- StoreKit Testing in Xcode
- Attach a StoreKit configuration to the run scheme. Iterate on the paywall and entitlement logic, and use Xcode’s transaction tools to create repeatable conditions. A local test uses the configuration available to the scheme.
- Sandbox
- Run a device build with the local StoreKit configuration disabled, using products configured in App Store Connect and a Sandbox Apple Account. Verify the real product identifiers, pricing display and renewal behaviour. If you have a backend, verify its sandbox transaction handling too.
- TestFlight
- Install the exact beta build testers will receive and repeat purchase and restore on device. Purchases in TestFlight use Sandbox. Record the build number and the outcome separately from the build's availability.
Keep separate evidence for each environment. This makes it possible to tell whether a failure comes from app logic, product setup or the distributed build. Apple’s test environment comparison
Read verified entitlements before updating access
Use a single entitlement state in the app. For a simple app that manages access locally, rebuild the active product set from Transaction.currentEntitlements at startup and after a verified purchase or transaction update. Map that set to your actual tiers. The function below reads only the subscription identifiers your feature accepts.
import StoreKit
@available(iOS 15.0, macOS 12.0, *)
func activeSubscriptionIDs(allowing productIDs: Set<String>) async -> Set<String> {
var active = Set<String>()
for await result in Transaction.currentEntitlements {
guard case .verified(let transaction) = result,
transaction.productType == .autoRenewable,
productIDs.contains(transaction.productID)
else { continue }
active.insert(transaction.productID)
}
return active
}
@available(iOS 15.0, macOS 12.0, *)
func restoreSubscriptions(allowing productIDs: Set<String>) async throws -> Set<String> {
try await AppStore.sync()
return await activeSubscriptionIDs(allowing: productIDs)
}
Call restoreSubscriptions from a user-initiated Restore Purchases action, since AppStore.sync() can prompt for authentication. Normal startup can read current entitlements directly. Show a clear result when the set is empty and a recoverable error if sync fails. Apple’s restore guidance
Start listening to Transaction.updates when the app launches. Verify each result, deliver the purchase before finishing it, and refresh access through your state owner. Handle a pending purchase as pending and leave access unchanged until a verified transaction arrives. A backend-managed app should reconcile access with its server before treating delivery as complete. Apple’s current entitlements API
Run a small acceptance matrix
- Fresh purchase. Start with an eligible test account. Confirm the product loads with the displayed billing period and price. Complete a purchase, unlock access, then relaunch and confirm the same tier.
- Cancel and pending. Dismiss the purchase sheet and check that access stays unchanged. Use a test scenario that produces a pending purchase, then verify that access updates only after approval arrives.
- Restore. Reinstall or use a second compatible device with the same test account and active entitlement. Confirm access can be read at launch, then exercise the restore action and verify the resulting tier. Repeat with an account that has nothing to restore.
- Renewal and cancellation. Observe an accelerated renewal. Turn off auto-renew and confirm access remains while the subscription is active. Once the period expires, verify that paid features lock when the entitlement is gone.
- Grace period and revocation. If you offer a billing grace period, verify the access policy through its end. Test a refund or revocation and confirm that the app refreshes its access state.
Record what passed
For each run, capture the environment, build, device, OS, product identifier, test account state and expected versus observed access. A TestFlight build marked available is a distribution milestone. Record an actual purchase and restore as separate checks before you call the subscription flow verified.
When you're ready to prepare the version for review, continue with the App Store launch checklist.