ADR 0007 — iOS subscription billing: Apple In-App Purchase
Status: Accepted (user decision 2026-04-22) Date: 2026-04-22 Deciders: software-architect + user Scope: How paid subscriptions are sold to users of the Raxx iOS app
Context
Raxx's subscription tiers (Free / Pro / Pro+) are sold on the web via Stripe at raxx.app/pricing. The iOS companion app needs a billing posture: how do iOS users become paying subscribers?
Three options exist:
- Apple In-App Purchase (IAP) via StoreKit 2 — in-app subscription flow, Apple handles checkout, receipt, renewals, refunds. Apple takes 15% (Small Business Program, <$1M annual net revenue) or 30% (above).
- External link-out to
raxx.app/pricing— post-Epic v Apple (US, 2024+), apps may present a URL that leaves the app for web purchase. Requires Apple's external-purchase disclosure sheet. 0% Apple cut. Some user drop-off. EU and other regions have different rules. - Web-only signup — iOS app requires an existing web-sourced subscription; no billing UI in the app at all. 0% Apple cut. Highest friction.
Decision
Adopt Apple IAP (StoreKit 2) for iOS subscriptions.
User's stated rationale: "fine using in-app. I like the security and control point for our users. No games."
Interpretation: Raxx values the native Apple subscription-management experience (single place to cancel, Apple-vetted payment, transparent pricing in Settings > Subscriptions) over the 15% margin hit. The "no games" comment rejects the link-out gymnastics and the adversarial relationship with Apple that comes with it.
Consequences
Positive
- User trust. Native Apple subscription management is the gold standard on iOS — users can see, pause, cancel in Settings without needing to know where Raxx is. Cancellation friction is a retention positive, not a business negative.
- App Store review path is clean. No external-purchase disclosure sheet, no guideline 3.1.1 tension, no need to defend a link-out decision to a reviewer.
- Family Sharing is native. StoreKit 2 subscriptions can optionally participate in Family Sharing with a single API flag.
- Refund handling is Apple's problem. Disputes and refunds go through Apple, not Raxx support.
Negative / costs
- 15% of iOS subscription revenue (30% if we clear $1M/year). At $29/mo Pro × 1000 iOS subs × 12 months = $348k gross, Apple cut $52k first year. Real money.
- Separate backend subscription-state path. Raptor needs:
/api/subscriptions/apple/notifications— Apple's server-to-server subscription state notifications (v2)subscription_sourcecolumn on users or a separatesubscriptionstable — "web" (Stripe) vs "ios" (Apple) vs "android" (Google Play future)- Receipt-validation logic calling Apple's App Store Server API for initial transaction
- Grace-period handling for billing retries
- Handling same-user-dual-subscription edge case (user subscribes on web AND iOS — refuse second, refund, or sync)
- Pricing ladder must use Apple's tier system. $29/mo → tier 29 ($29.99) or manually price at a different nearest tier. Need to accept $29.99 vs $29 parity drift or adjust web to match.
- StoreKit 2 requires Swift + iOS 15+. iOS 15 floor matches the existing iOS 16 floor decision in ADR 0004.
Neutral
- User cannot upgrade from Pro to Pro+ cross-platform without handling the billing provider switch. Apple allows same-group upgrades within IAP; web-sourced upgrades happen on web. Cross-billing upgrades are explicitly not supported in v1.
Alternatives considered
External link-out (rejected)
Post-Epic v Apple, US apps may link externally. But: - User explicitly said "no games" — rejecting the adversarial posture - Disclosure sheet creates friction at the worst moment (conversion) - EU / Asia / rest-of-world rules still vary; global compliance is a maintenance tax - Apple's external-purchase commission (27% — only 3% less than IAP) in some jurisdictions eliminates the margin benefit
Web-only signup (rejected)
- Highest friction for iOS-first users who want to try Raxx natively
- User explicitly rejected this implicitly by choosing IAP
- Forces a web detour for every new iOS signup — hurts first-touch conversion
Compliance checklist
- [ ] App Store Connect: declare subscription group, auto-renewable subscription products for each tier
- [ ] App Store Connect: configure introductory offer if Founders promo extends to iOS
- [ ] Backend: implement server-to-server notifications v2 endpoint with JWS validation
- [ ] Backend: store
original_transaction_idper subscription, not the rollingtransaction_id - [ ] Backend: subscription status is source-of-truth on Raptor, iOS receipt is authoritative ingredient
- [ ] Privacy: Apple requires declaring subscription product IDs in
App Store > App Privacy— no PII exposure, just metadata - [ ] Tax: Apple handles tax collection in-checkout for regions where required; web-Stripe path has its own tax setup
Revisit when
- Annual revenue approaches $1M (tier jumps to 30%, may change economics)
- EU Digital Markets Act forces change in Apple's IAP terms (ongoing; watch 2026+ rulings)
- Adding Android — Google Play IAP has similar mechanics but different API surface; will warrant a sibling ADR
- User pattern emerges where a material fraction of iOS users request external-purchase option
Downstream cards (for product-manager to file under Epic #167)
- Backend: receipt-validation service + webhook endpoint for Apple notifications v2
- Backend: dual-source subscription reconciliation (web-Stripe + iOS-Apple)
- iOS: StoreKit 2 subscription UI + purchase flow
- iOS: restore-purchases flow (required by App Store guideline 3.1.1)
- Ops: App Store Connect tenant setup + subscription product creation (blocked on Apple Developer org account — see #167 open question 3)
- Legal: ensure Terms of Service + EULA cover both billing paths (Stripe + Apple)