StoreKit Restore Purchases: Restore Lifetime App Access
StoreKit restore won't detect legacy App Store lifetime purchases. Validate receipts or use StoreKit 2/AppTransaction to preserve lifetime access securely.
Will StoreKit’s Restore Purchases function detect and restore lifetime access for users who previously purchased the app directly from the App Store before switching to StoreKit?
App setup details:
- Pricing: $3.99/month subscription with 5-day free trial (implemented via consumables, as non-consumables do not support free trials), and $9.99 lifetime one-time purchase.
- Goal: Ensure existing App Store buyers retain lifetime access after migrating to StoreKit.
No, StoreKit’s restore purchases function won’t automatically detect or restore lifetime access for users who bought your app directly from the App Store before switching to StoreKit in-app purchases. Those original paid downloads—think the classic $9.99 upfront buy—live in the App Store receipt, not StoreKit’s transaction history, so you’ll need to validate that receipt yourself to spot the lifetime purchase and grant entitlements manually. This keeps your existing customers happy during migration without forcing repurchases, especially alongside your $3.99/month sub (with that clever 5-day trial via consumables) and $9.99 IAP lifetime option.
Contents
- How StoreKit Restore Purchases Works
- Why Pre-StoreKit App Store Purchases Aren’t Detected
- Validating App Store Receipts for Lifetime Access
- StoreKit 2 Upgrades: AppTransaction and Beyond
- Step-by-Step Migration for Your App Setup
- Testing and Best Practices
- Sources
- Conclusion
How StoreKit Restore Purchases Works
Picture this: a user reinstalls your app or switches devices. They tap “Restore Purchases,” expecting magic. StoreKit delivers by fetching transactions tied to their Apple ID—but only those made through StoreKit itself.
For your setup, that covers the new $9.99 lifetime purchase IAP or sub trials. Call restoreCompletedTransactions() in StoreKit 1, or lean on Transaction.currentEntitlements in StoreKit 2 for async bliss. It grabs non-consumables and active subs, verifies them, and lets you unlock features. Simple for new buys.
But here’s the catch for migrations. Apple’s official docs stress implementing a restore interface, yet they don’t promise it’ll snag pre-StoreKit history. Why? Those old App Store purchases aren’t “StoreKit transactions.” They’re receipt-based, from the era when users dropped cash directly on your app page.
Users love this flow when it works. No repurchase pain. Just seamless access. And for subs? It reactivates trials or renewals too. Yet for lifetime access from paid apps, it’s a no-go without extra legwork.
Why Pre-StoreKit App Store Purchases Aren’t Detected
Ever wonder why Apple draws this line? StoreKit kicked off with iOS 14ish APIs, but many apps sold upfront via App Store for years. A lifetime purchase back then? Pure receipt data, not a transaction object.
Hit restore, and StoreKit scans its own ledger. Empty for old-timers. Stack Overflow devs nailed it: “StoreKit’s ‘Restore Purchases’ only restores purchases that were made through StoreKit.” Pre-switch buys? Invisible.
Your $3.99 sub folks? Fine if post-StoreKit. But original App Store payers? They’ll see a paywall. Frustrating. RevenueCat echoes this in their StoreKit migration guide: Switch to new entities like Transaction, but legacy receipts need separate handling.
Short version: Automatic restore = StoreKit-only. Legacy App Store purchases demand receipt parsing. Ignore it, and loyal users bail.
Validating App Store Receipts for Lifetime Access
So, how do you fix it? Dive into the receipt. It’s a base64 blob in the app bundle, holding all purchase history.
Step one: Grab it with Bundle.main.appStoreReceiptURL. Missing? Trigger SKReceiptRefreshRequest. Then POST to Apple’s verifyReceipt endpoint—sandbox for dev, buy.itunes.apple.com for prod, per Apple Forums advice.
Parse the JSON response. Hunt original_application_version. If it’s ≤ your paid app version (say, “4.0”), boom—lifetime access granted. No IAP needed.
For product IDs, scan “in_app” or “latest_receipt_info” for your lifetime identifier. Server-side validation? Smarter for security. Adapty breaks it down: Always finish transactions post-restore to avoid retries.
Code snippet (StoreKit 1 style):
let receiptURL = Bundle.main.appStoreReceiptURL
let receiptData = try Data(contentsOf: receiptURL!)
let receiptString = receiptData.base64EncodedString()
Your sub trial via consumables? Receipt flags those too, but focus non-consumables for lifetime.
Users on iOS 14? Stick to this. Newer? StoreKit 2 shines brighter.
StoreKit 2 Upgrades: AppTransaction and Beyond
StoreKit 2 changes everything—for the better. No more clunky restoreCompletedTransactions. Just AppStore.syncTransactions or Transaction.currentEntitlements. Offline? Still works, caching like a champ, as RevenueCat’s tutorial shows.
Big win: iOS 17’s AppTransaction. One call: AppTransaction.shared. It spills original purchase date and version. No receipt dance.
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result,
transaction.productType == .nonConsumable {
// Lifetime unlocked!
await transaction.finish()
}
}
Pre-StoreKit? Combine with receipt check, or use originalPurchaseDate (iOS 16+). Tanas Chita’s guide mixes both for full coverage.
Your app? iOS 15+ friendly. Subs auto-sync. Lifetime purchase? Map old receipt to new entitlements.
Downside: iOS 14 support means hybrid code. But worth it.
Step-by-Step Migration for Your App Setup
Tailored to you: $3.99/month (consumable trial hack—smart, since non-consumables skip trials), $9.99 lifetime IAP.
-
Launch Check: On app start, validate receipt and entitlements. Grant lifetime if old purchase found.
-
Restore Button: Hybrid—StoreKit restore + receipt refresh. UI shows “Checking history…”
-
Entitlements Mapping:
| Old Purchase | Action |
|--------------|--------|
| App Store Paid | Lifetime via receipt version |
| StoreKit Lifetime IAP | Transaction.currentEntitlements |
| Sub Active | RenewalInfo for trial/reactivate | -
Server Sync: Verify receipts backend. Use App Store Server API for status.
-
Grandfathering:
originalPurchaseDateflags pre-sub users for free lifetime? Your call.
Delasign’s Swift tutorial adds Xcode config files for sim restores. Test sandbox buys first.
Edge cases: Refunds? Receipt shows. Family Sharing? Propagates.
Testing and Best Practices
Testing’s where migrations shine or flop. Sandbox accounts mimic, but old receipts? Tricky.
- Xcode StoreKit Config: Local transactions. Add “paid app” sim via custom products.
- TestFlight: Real receipts.
- Multiple IDs: One with paid history, one fresh.
Best practices? User feedback: “Restoring…” spinner. Errors? “No purchases found—buy now?” Finish every transaction. Log everything.
From Apple docs: Non-consumables get permanent grants post-restore. Subs? Prorate if needed.
Scale with RevenueCat or similar? They handle receipts auto. But DIY works.
Miss this, and reviews tank: “Paid before, now paywall?!” Nail it, and retention soars.
Sources
- Restoring purchased products - Apple Developer Documentation
- ios - How restore with StoreKit 2? - Stack Overflow
- StoreKit 1 vs 2: How to Migrate to StoreKit 2 - RevenueCat
- What’s the easiest way to check wh… - Apple Developer Forums
- What Does “Restore Purchase” Mean on Apps? - Adapty
- How to code and test restore purchases in Swift and Xcode - Delasign
- iOS In-App Subscription Tutorial with StoreKit 2 and Swift - RevenueCat
- How to restore in-app purchases with StoreKit 2 for iOS - Tanas Chita
Conclusion
StoreKit’s restore purchases skips legacy App Store lifetime purchases, but receipt validation or AppTransaction bridges the gap perfectly for your migration. Implement hybrid checks on launch and restore, test rigorously with configs, and those original buyers stay lifetime VIPs—no repurchase drama. Get this right, and your app’s ready for subs, IAP, and happy users across iOS versions.