Dev Diary Day 5: StoreKit 2 Subscription Implementation for a Memo-to-Email App
Summary
This dev diary details the complete StoreKit 2 subscription implementation for "Simple Memo," a memo-to-email iOS app, focusing on both code and critical non-code requirements for App Store review. The app uses a three-tier monetization model: a 7-day free trial, a free plan with 3 sends/day, and an unlimited Premium plan. Key technical aspects include using StoreKit 2 with Swift Concurrency for on-device transaction verification, a `SubscriptionManager` singleton with `@MainActor` and `ObservableObject`, and a `Task.detached` transaction listener for `Transaction.updates`. The implementation also covers robust product loading with retry mechanisms, handling `product.purchase()` outcomes (success, userCancelled, pending), and mandatory "Restore Purchases" functionality via `AppStore.sync()`. The author emphasizes that 80% of in-app purchase challenges lie outside the code, primarily in App Store Connect configuration, Guideline 3.1.2 compliance, and navigating Sandbox testing quirks.
Key takeaway
For iOS developers implementing in-app subscriptions, you must meticulously address Apple's Guideline 3.1.2 requirements, including clear pricing, auto-renewal disclosure, legal links, and a "Restore Purchases" button. Your paywall UI and underlying StoreKit 2 implementation should anticipate and gracefully handle network issues and Sandbox environment inconsistencies to prevent user frustration and App Store rejections. Focus on a robust, user-centric payment flow from the outset.
Key insights
StoreKit 2 simplifies iOS in-app purchases, but App Store review compliance and robust error handling remain critical.
Principles
- Design for graceful failure in payment flows.
- Prioritize user trust over aggressive monetization.
- Compliance is as important as code quality.
Method
Implement StoreKit 2 using Swift Concurrency, a `@MainActor` singleton for state, and a `Task.detached` listener for `Transaction.updates`, ensuring `transaction.finish()` is called post-processing.
In practice
- Use `Task.detached` for background transaction listening.
- Implement a retry button for `Product.products(for:)` failures.
- Differentiate paywall context for better UX.
Topics
- StoreKit 2
- iOS In-App Purchases
- App Store Review
- Swift Concurrency
- Subscription Management
Best for: Software Engineer, Product Manager
Related on AIssential
Editorial summary, takeaway, and curation by AIssential. Original article published by LLM on Medium.