StoreKit 2 subscriptions, implemented end to end
StoreKit 2 is Apple’s Swift-native API for in-app purchases. It loads products, runs purchases with async/await and returns transactions signed by the App Store, so the app can verify them without parsing receipts. Most paid iOS apps today run on some mix of:
- Auto-renewable subscriptions: monthly and yearly plans in a subscription group.
- Non-consumables: a lifetime unlock or a one-off feature pack.
- A free tier: the app is useful without paying, and the paid tier removes limits.
The purchase button is the easy part. The work is making sure every user sees exactly what they paid for, on every device, after every renewal, refund and reinstall.
Where StoreKit 2 implementations go wrong
Missing transaction updates
Purchases do not only happen behind your paywall. Renewals, Ask to Buy approvals, purchases on another device and offer code redemptions arrive through Transaction.updates. The app must start listening at launch, verify each VerificationResult, update entitlements and call finish(). Apps that skip this have users who paid and still see the paywall.
Entitlements computed in the wrong place
Transaction.currentEntitlements is the source of truth for what the user owns right now. I compute access from it in one entitlement service that the whole app observes, instead of caching an “isPro” flag at purchase time. That one decision prevents a whole class of “I paid but it’s locked” support emails. Revoked transactions, for example after a refund, carry a revocationDate and must remove access.
Subscription edge cases
Billing problems, grace periods, upgrades and downgrades within a group, and pending purchases each need a defined behavior. Product.SubscriptionInfo exposes status and renewal information, so the app can show “your payment failed” instead of silently locking features. I design these states with you up front rather than discovering them in reviews.
Restore and multiple devices
AppStore.sync() exists for the restore button, but a correct entitlement model means users rarely need it. Mixing a lifetime product with subscriptions needs the same care: the lifetime purchase must win regardless of subscription state.
Server side, when you need it
If access must also work on a website or another platform, a backend should verify signed transactions with the App Store Server API and receive App Store Server Notifications V2 for renewals, refunds and expirations. That keeps the server and the app agreeing on who has access.
Testing and review
A local StoreKit configuration file makes renewals and refunds testable in minutes. App Review checks that pricing is clear, restore works and the paywall explains what the user gets, so those are part of the implementation, not an afterthought.
Purchase models in my own apps
I use three different models across my own products, which is the best way to learn where each one breaks:
| App | Model | What it shows |
|---|---|---|
| Fokus Zone | Free, with a Premium subscription | A free core app; Premium unlocks unlimited restrictions, advanced scheduling and detailed analytics |
| My Disc Golf | Free, with PRO monthly, yearly or lifetime | Subscriptions and a one-time lifetime unlock granting the same features |
| Haptic Forge | One-time purchase | No subscription, no ads, no account, for a professional tool |
Each model asks different questions: what stays free, what a lifetime buyer gets when new features arrive, and how the app behaves when a subscription lapses. Choosing a model is a product decision as much as a technical one, and I am happy to talk it through with you.
In-app purchase work I take on
- A full StoreKit 2 setup: products, paywall, entitlement service, restore, and analytics hooks.
- Hybrid models: free tier, subscriptions and lifetime unlocks living together.
- Migration from original StoreKit or a third-party purchase SDK to StoreKit 2. See app rescue and modernization.
- Server-side verification and App Store Server Notifications handling with your backend team.
- A review of an existing setup that has “paid but locked” complaints.
Get your purchases right
Tell me your pricing model and where users hit the paywall, at contact@hobbysoft.pro. I will point out the edge cases before your users find them.