ShopKit — Requirements
Personas
- Browser — opens the app, scrolls free articles, tries to open a Pro one, sees the paywall, decides whether to start a free trial.
- Trialist — starts a free 7-day trial, uses Pro features, weighs converting.
- Returning subscriber — has an active subscription, opens the app, immediately accesses Pro content.
- Lapsed user — was subscribed, canceled, comes back to the paywall — should see a “welcome back” offer if eligible.
User stories
Onboarding
- First launch shows a 3-screen onboarding (what the app does, free vs Pro, “sign up to save reading progress”).
- Sign up uses Sign in with Apple — no email/password forms.
- The sign-up screen is optional skip, but limits sync.
Browsing
- Home shows the article catalog from a remote API; each article card shows title, author, “Pro” badge, estimated read time.
- Pull-to-refresh reloads. Failed loads show a non-modal error toast, never a blank screen.
- Tapping a free article opens it immediately.
- Tapping a Pro article either opens it (if subscribed) or routes to the paywall.
Paywall
- Paywall shows: tier name, monthly + annual prices (with “save 30%” badge on annual), feature list, “Start 7-day free trial” CTA, fine print (auto-renews, manage in Settings, restore button).
- Annual is the default selection (highest LTV).
- Free trial introductory offer is available only to users who haven’t previously subscribed (StoreKit handles this).
- Restore Purchases button is prominent (Apple Review requires).
- Privacy & Terms links are visible (Apple Review requires for subscriptions).
Subscription flow
- Tapping “Start trial” presents Apple’s purchase sheet via
Product.purchase(). - After successful purchase, the user lands on the article that triggered the paywall.
- If purchase fails (cancelled, payment issue), the user returns to the paywall with a contextual message.
- Active subscription gates every Pro article check via
SubscriptionStatus.current.
Account
- Settings → Manage Subscription opens Apple’s
manageSubscriptionsSheet. - Settings → Account shows the Sign in with Apple email (or “anonymous”) and a Sign Out button.
- Sign out clears the Keychain token; does NOT cancel the subscription (the user must do that in Apple Settings).
- Delete Account: deletes the user’s server-side data and signs out. Subscription continues per Apple’s lifecycle.
Networking
- All API calls go through the typed
APIClient. NoURLSession.sharedcalls anywhere in the app. - Auth token is attached to every request via an injected
AuthProvider. - 401 responses trigger a one-time token refresh; if refresh fails, sign out gracefully.
- Network errors are mapped to typed
APIErrorcases (unauthorized, notFound, server, decoding, transport).
Acceptance criteria
- Cold launch to home: < 1.5 s
- Paywall display to purchase sheet: < 500 ms after tap
- Subscription state change (purchase, restore, refund) reflects in UI within < 2 s
- Test the full flow with sandbox accounts for: purchase, cancel during trial, refund-after-purchase, sub-billing-retry
- GitHub Actions: PR build + test < 18 min; merge-to-main → TestFlight < 30 min
Non-goals
- No social features (comments, sharing).
- No offline reading. (Stretch goal.)
- No DRM or content encryption — the Pro gate is purely server-side via subscription status.
- No support for unlimited tiers — only one Pro tier (monthly + annual).
- No promotional offers (besides the standard 7-day free trial).
Constraints
- iOS 17+ (StoreKit 2 latest,
manageSubscriptionsSheet, Sign in with Apple). - Apple Developer Program — Sign in with Apple capability + StoreKit + WeatherKit-style entitlements.
- Demo server: small Express + Postgres for the article catalog and user data (not part of this capstone — provided via Docker Compose in the starter).
Next: Architecture