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

  1. First launch shows a 3-screen onboarding (what the app does, free vs Pro, “sign up to save reading progress”).
  2. Sign up uses Sign in with Apple — no email/password forms.
  3. The sign-up screen is optional skip, but limits sync.

Browsing

  1. Home shows the article catalog from a remote API; each article card shows title, author, “Pro” badge, estimated read time.
  2. Pull-to-refresh reloads. Failed loads show a non-modal error toast, never a blank screen.
  3. Tapping a free article opens it immediately.
  4. Tapping a Pro article either opens it (if subscribed) or routes to the paywall.

Paywall

  1. 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).
  2. Annual is the default selection (highest LTV).
  3. Free trial introductory offer is available only to users who haven’t previously subscribed (StoreKit handles this).
  4. Restore Purchases button is prominent (Apple Review requires).
  5. Privacy & Terms links are visible (Apple Review requires for subscriptions).

Subscription flow

  1. Tapping “Start trial” presents Apple’s purchase sheet via Product.purchase().
  2. After successful purchase, the user lands on the article that triggered the paywall.
  3. If purchase fails (cancelled, payment issue), the user returns to the paywall with a contextual message.
  4. Active subscription gates every Pro article check via SubscriptionStatus.current.

Account

  1. Settings → Manage Subscription opens Apple’s manageSubscriptionsSheet.
  2. Settings → Account shows the Sign in with Apple email (or “anonymous”) and a Sign Out button.
  3. Sign out clears the Keychain token; does NOT cancel the subscription (the user must do that in Apple Settings).
  4. Delete Account: deletes the user’s server-side data and signs out. Subscription continues per Apple’s lifecycle.

Networking

  1. All API calls go through the typed APIClient. No URLSession.shared calls anywhere in the app.
  2. Auth token is attached to every request via an injected AuthProvider.
  3. 401 responses trigger a one-time token refresh; if refresh fails, sign out gracefully.
  4. Network errors are mapped to typed APIError cases (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