iOS Subscription Reintroduction Plan
Reader and action
This document is for a future engineer or agent planning to reintroduce user subscriptions in the iOS app after the free-app cleanup. After reading it, they should be able to draft an implementation checkpoint with the right dependencies, sequencing, verification gates, and rollback plan.
This is a planning document, not an implementation runbook. Do not treat any section as permission to enable paid access without a new checkpoint, product decision, legal review, backend contract review, and App Store release plan.
Current baseline
The current iOS app is free-only.
The prior iOS subscription surface was deliberately removed rather than hidden. The app no longer ships a StoreKit billing service, local StoreKit configuration, subscription Settings route, restore-purchases UI, Pro/Premium plan state, local isPro gating, paid-tier BYOK response handling, or paid-plan copy in active iOS user-facing flows.
Active legal copy also describes the current iOS build as free, with no paid feature paywalls. Reintroducing subscriptions therefore changes more than UI. It changes product behavior, account lifecycle handling, App Review metadata, legal copy, privacy disclosure assumptions, backend entitlement semantics, and cross-platform parity expectations.
Decision gate before implementation
Before writing code, make these decisions explicitly and record them in planning artifacts:
- Subscription purpose — What does a paid subscription unlock?
- Higher TTS/STT quotas?
- Voice clone creation/deletion?
- Soundboard category limits?
- BYOK cloud storage/resolution?
- Priority processing or future-only features?
- Free tier contract — What remains available without payment?
- Entitlement source of truth — Which backend table/function determines whether a user is active paid, expired, trialing, admin, or legacy?
- Billing authority — Is Apple StoreKit the only iOS purchase authority, or can web/Stripe entitlements also unlock iOS features?
- Restore behavior — What should Restore Purchases do when Apple reports no active transaction but backend has a legacy entitlement?
- Account deletion behavior — Should paid/legacy users be blocked, warned, routed to support, or allowed to delete with entitlement cleanup?
- Cross-surface parity — Should Android/web gain the same subscription behavior at the same time, or is iOS intentionally first?
- Rollback model — If App Review or production billing fails, can the app return to free-only without data loss or stuck entitlements?
If any answer is unknown, plan a discovery slice before implementation.
Non-goals for an iOS-only reintroduction
Do not do these accidentally while restoring iOS subscriptions:
- Do not put service-role behavior, entitlement mutation, or privileged account cleanup in the iOS client.
- Do not trust local StoreKit state as the only authorization source for backend-gated features.
- Do not show subscription UI unless backend entitlement checks and restore/sync behavior are defined.
- Do not reintroduce raw backend account-deletion errors into user-facing iOS copy.
- Do not weaken sign-in, validation, preflight, quota, provider-error, or abuse-prevention boundaries just because a paid tier exists.
- Do not update App Store Connect, legal policy, or pricing externally without explicit human approval.
Required architecture
1. StoreKit purchase and restore layer
A restored iOS subscription implementation needs a StoreKit 2 boundary that owns:
- product loading
- purchase initiation
- transaction observation
- transaction verification handoff
- restore purchases
- subscription status refresh
- retryable purchase/sync errors
- redacted diagnostics
The client may observe Apple transactions, but backend authorization should still be confirmed through an authenticated server boundary. The iOS app should not decide permanently that a user is Pro solely because a local transaction appears valid.
2. Backend entitlement sync
The backend must expose authenticated server-side endpoints for Apple subscription sync. The endpoint contract should define:
- request shape for Apple transaction identifiers or signed transaction data
- server-side verification behavior
- idempotency rules for repeated sync attempts
- entitlement state mapping
- expiry, grace period, billing retry, refund, revoke, and family sharing behavior
- stable public error codes
- redacted logs and diagnostics
The iOS app should consume a normalized entitlement response rather than raw provider or database details.
3. Auth state and entitlement state
Do not overload signed-in state with billing behavior unless the distinction is intentional and tested. Prefer a separate entitlement model with states such as:
- unknown/loading
- free
- trialing
- active paid
- grace period
- billing retry
- expired
- legacy review required
- admin/internal
The UI can derive display copy from that model, but backend-gated actions should still use server enforcement.
4. Feature gates
Each paid feature needs an explicit gate contract:
| Feature family |
Required decision |
Backend enforcement required? |
Local UI behavior |
| TTS quota |
Daily/period limit per tier |
Yes |
Explain limit and reset/upgrade path |
| STT quota |
Byte/minute limit per tier |
Yes |
Explain limit and reset/upgrade path |
| Voice clone |
Whether creation/deletion is paid-only |
Yes |
Block before upload when entitlement is known insufficient |
| Soundboard |
Whether category/clip limits return |
Yes for durable limits |
Show limit before create/reorder where possible |
| BYOK |
Whether paid tier changes cloud BYOK behavior |
Yes if server storage is used |
Keep local-key fallback separate from paid cloud behavior |
Do not restore a local UI gate unless the backend has an equivalent enforcement point or the gate is explicitly cosmetic.
Suggested checkpoint decomposition
Slice 1 — Product and contract brief
Produce the paid-tier contract before code changes.
Deliverables:
- tier matrix
- feature-gate matrix
- entitlement state model
- account deletion policy
- restore-purchases policy
- App Store/legal impact checklist
- rollback criteria
Verification:
- planning review confirms no unknown gate or lifecycle state is hidden in implementation work
- legal/release owner signs off on paid-app copy direction before code starts
Slice 2 — Backend entitlement and Apple sync contract
Implement or verify the authenticated backend contract that iOS will call after StoreKit purchases and restores.
Deliverables:
- Apple subscription sync endpoint contract
- entitlement refresh endpoint contract
- stable public error codes
- idempotent transaction handling
- redacted diagnostic labels
- tests for active, expired, revoked, duplicate, malformed, and legacy states
Verification:
- backend tests prove entitlement state transitions
- no service-role behavior is exposed to clients
- account-deletion behavior is tested for paid, expired, and legacy states
Slice 3 — iOS StoreKit runtime restoration
Restore the iOS StoreKit service boundary without exposing subscription UI everywhere yet.
Deliverables:
- product loading
- purchase flow
- transaction listener
- restore purchases
- backend sync call
- entitlement refresh model
- user-visible loading/error states
- redacted diagnostics
Verification:
- StoreKit local tests or StoreKit configuration tests cover purchase, restore, cancellation, and sync failure
- app startup does not block indefinitely on billing product load
- failed billing sync leaves a clear recovery path
Slice 4 — iOS Settings/paywall UI
Restore user-facing subscription surfaces only after runtime and backend contracts are testable.
Deliverables:
- subscription entry point in Settings
- paywall/product list
- purchase and restore buttons
- current plan/status display
- upgrade/manage subscription copy
- support/recovery copy
Verification:
- Settings navigation tests prove the route is intentionally visible
- UI tests or view-model tests cover loading, purchase success, purchase cancellation, restore success, restore no-op, and failure messages
- static copy scans confirm no stale free-only legal contradiction remains
Slice 5 — Feature gates
Restore feature-specific paid behavior one gate family at a time.
Recommended order:
- Read-only status display and entitlement refresh
- TTS/STT quota copy and local preflight hints
- Voice clone local blocked reason and disabled button
- Soundboard limit copy and create/category guard
- BYOK cloud behavior, only if explicitly included in the paid tier contract
Verification:
- each feature keeps sign-in, validation, preflight, backend error, and provider failure behavior intact
- backend enforcement and iOS local UX agree for each gate
- free, paid, expired, admin, and unknown entitlement states are covered
Slice 6 — Legal, App Review, and release readiness
Update user-facing legal and release surfaces after the paid product contract is stable.
Deliverables:
- Terms/EULA subscription section
- Privacy Policy purchase/entitlement data disclosure if applicable
- App Store Connect subscription products and metadata
- App Store privacy labels review
- screenshots/paywall review if required
- release notes and support messaging
Verification:
- active legal copy no longer says the current app has no paid feature paywalls
- StoreKit product identifiers match backend configuration and App Store Connect
- account deletion, restore, cancellation, and refund support paths are documented
Slice 7 — Rollout and rollback
Ship behind a deliberate rollout boundary.
Deliverables:
- release checklist
- smoke test plan
- support escalation script
- rollback plan to free-only mode
- monitoring/diagnostic checklist
Verification:
- TestFlight smoke covers purchase, restore, entitlement refresh, gated feature access, account deletion, and sign-out/sign-in continuity
- rollback can hide paywall UI and disable local gates without corrupting entitlements
iOS implementation details to plan explicitly
StoreKit service behavior
The service should not block app launch forever. Product loading should have a timeout or independent loading state. Startup can show paid status as unknown while the rest of the app remains usable.
Purchase flow should distinguish:
- user cancelled
- product unavailable
- pending approval
- transaction unverified locally
- backend sync failed
- backend accepted but entitlement refresh failed
- network unavailable
- unknown StoreKit error
Each case needs fixed public copy and redacted diagnostics.
Restore purchases behavior
Restore must be idempotent and safe to retry. A successful Apple restore should still sync with backend before backend-gated features unlock. If no transactions are restored, the UI should explain that no active Apple subscription was found and point to support if the user believes that is wrong.
Account deletion behavior
Do not silently hide recovery guidance. Decide whether active paid users can delete directly. If deletion is blocked for legacy or paid state, iOS should show fixed public copy and support guidance, not raw backend messages.
Entitlement refresh behavior
The app needs an explicit refresh action or automatic refresh points after:
- sign-in/session restore
- purchase success
- restore success
- app foreground
- backend sync retry
- account deletion cancellation/failure
Unknown entitlement state should not accidentally grant paid access.
Offline behavior
Decide what paid features do offline. Reasonable default:
- already-downloaded local data remains accessible
- new backend-paid actions require fresh auth/entitlement where server enforcement applies
- UI shows entitlement as unknown/offline rather than free or paid
Diagnostics and observability
Use bounded labels and state transitions. Do not log:
- Apple transaction payloads
- purchase tokens
- Supabase JWTs
- auth tokens
- user IDs in public logs
- provider keys
- generated text
- transcripts
- voice samples
- signed storage URLs
Useful diagnostic labels include:
storekit_products_loaded
storekit_products_unavailable
storekit_purchase_cancelled
storekit_purchase_pending
storekit_transaction_unverified
apple_subscription_sync_failed
entitlement_refresh_failed
restore_no_active_subscription
subscription_gate_state_unknown
Verification matrix
| Area |
Minimum verification |
| Build |
iOS app builds on a concrete simulator destination |
| StoreKit |
Local StoreKit tests cover purchase, cancellation, pending, restore, and failure |
| Backend sync |
Backend tests cover transaction verification, idempotency, expiry, revoke, malformed input, and stable errors |
| Settings UI |
Tests prove subscription route is visible only when intentionally enabled |
| Voice clone |
Tests cover free blocked, paid allowed, unknown blocked, validation preserved, preflight preserved |
| TTS/STT |
Tests cover quota copy and backend quota rejection mapping |
| Soundboard |
Tests cover paid/free limit copy and backend category-cap behavior if included |
| Account deletion |
Tests cover active paid, expired, legacy, and failed backend responses with fixed copy |
| Legal copy |
Static scan ensures active legal copy matches paid behavior |
| Privacy |
App Store privacy labels and policy mention purchase/entitlement data if collected |
| Rollback |
Tests or static checks prove paid UI/gates can be disabled without deleting entitlement history |
Use concrete simulator destinations for XCTest. Generic iOS simulator destinations cannot run unit tests for this project.
Rollback plan
A safe rollback should be planned before release.
Rollback should be able to:
- Hide iOS subscription UI.
- Stop local paid-feature gates from blocking free users.
- Keep backend entitlement history intact for audit/support.
- Disable or ignore quota/paid-tier enforcement through backend configuration.
- Preserve account deletion and support guidance.
- Restore free-app legal copy only if the public paid offer is fully withdrawn.
Do not roll back by deleting customer transaction history or entitlement records.
Documentation updates required when implementing
When actual implementation begins, update:
- active Terms/EULA
- active Privacy Policy
- release checklist
- App Store submission checklist
- subscription re-enable runbooks
- iOS test strategy notes
- user support/account deletion guidance
The docs should state the current state only. Historical planning notes can remain, but active policy copy must not contradict the released app.
Ready-to-plan checklist
Do not start implementation until these are true: