| name | subscription-disclosure |
| description | Detects missing or obscured subscription disclosure elements on paywall screens, enforcing Guideline 3.1.2(c) requirements for auto-renewal terms, trial end dates, and actual price. |
Skill: Subscription Disclosure
Purpose
Detects missing or obscured subscription disclosure elements (auto-renewal, trial end terms, cancellation instructions, actual price) on paywall screens, enforcing Guideline 3.1.2(c).
Apple Guideline
- Primary: 3.1.2(c) โ Business: Payments โ Subscriptions: Disclosure Requirements
- Related: 3.1.2(a), 3.1.2(b)
- Reference:
references/guidelines/3-business.md
Real-World Rejection Cases
-
Case: Paywall shows "50% OFF" prominently โ actual billing amount ($9.99/month) shown in 10pt gray text below fold โ rejected
Source: https://developer.apple.com/forums/thread/127616
Root cause: 3.1.2(c) requires billed amount shown clearly and conspicuously โ size/color/placement must not obscure it; discounts shown without clear pricing are a known rejection trigger
-
Case: Free trial paywall shows "Try free for 7 days" with no mention of what happens after trial โ rejected
Source: RevenueCat Community (https://community.revenuecat.com/general-questions-7/rejected-due-to-guideline-3-1-2-business-payments-subscriptions-4775)
Root cause: Must clearly state amount billed after trial ends and when billing starts
-
Case: App rejected for subscription auto-renewal not disclosed โ "billed amount not clearly displayed"
Source: blog.wenhaofree.com/en/posts/articles/app-store-guideline-3-1-2-subscription-fix/
Root cause: Auto-renewable subscription purchase flow must state renewal continues until cancelled
-
Case: Paywall had no links to Privacy Policy or Terms of Use โ reviewer required legal doc links before purchase could proceed โ rejected under 3.1.2(c)
Source: AngularCorp blog (angularcorp.com/en-us/blog/guideline-3-1-2)
Root cause: Subscription paywalls must include accessible links to Privacy Policy and Terms of Use; reviewers verify these links exist and are functional before approving
-
Case: Paywall showed price as "from $1.67/month" calculated from an annual plan โ reviewer expected to be billed $1.67/month but actual charge was $19.99/year โ rejected
Source: AppFollow (appfollow.io/blog/app-store-rejection-reasons)
Root cause: If billing is annual, the displayed price must be the annual amount or clearly labeled as "billed annually at $X"; showing only the monthly equivalent without disclosing actual charge is a known rejection pattern
Trigger
Invoke on any iOS/macOS project with subscription or in-app purchase paywalls.
Inputs
| Name | Type | Default | Description |
|---|
project_root | path | cwd | iOS/macOS project root |
shared_context | object | nil | Pre-collected context from appstore-full-audit Phase 1 |
Actions
Phase 1: Context Collection
Skip this phase if shared_context is provided.
Glob **/*Paywall* or **/*Subscription* or **/*Premium* or **/*Pricing* โ locate paywall and subscription UI files.
Glob **/*.swift โ collect all Swift source files for broader search.
Phase 2: Checks
Search primarily in paywall/subscription view files (*Paywall*, *Subscription*, *Premium*, *Pricing*). Fall back to all **/*.swift if no dedicated files found.
-
Auto-renewal disclosure
Grep pattern "auto-renew"|"automatically renew"|"renews automatically"|"auto renew"|"recurring" in subscription/paywall files.
If absent โ ๐ด CRITICAL. Every auto-renewable subscription paywall must disclose that the subscription renews automatically until cancelled.
-
Trial end disclosure
Grep pattern "after.*trial|trial ends|then.*per|after.*free|billed after|charged after" in subscription/paywall files.
If a free trial is offered but this disclosure is absent โ ๐ด CRITICAL. Users must know what they will be charged when the trial ends.
-
Cancellation information
Grep pattern "cancel anytime"|"cancelable"|"Cancel in Settings"|"manage.*subscription"|"cancell" in subscription/paywall files.
If absent โ ๐ HIGH. Instructions for cancellation must be accessible from the subscription UI.
-
Actual price display
Grep pattern displayPrice|localizedPrice|priceLocale|price\.formatted|priceString in subscription/paywall files.
If absent โ ๐ด CRITICAL. The actual billed price must be displayed, not only a discount label. Must use StoreKit's localized price APIs to show the correct price for the user's region.
-
Discount without price
Grep pattern "% off"|"save.*%"|"discount"|"OFF" in subscription/paywall files.
If found, verify displayPrice|localizedPrice also present in same file โ ๐ HIGH if price display absent. Showing a discount without the absolute price is a known rejection pattern.
-
Billing period disclosure
Grep pattern subscriptionPeriod|subscription_period|billingPeriod|"per month"|"per year"|"monthly"|"annually"|"weekly" in subscription/paywall files.
If absent โ ๐ HIGH. Billing frequency must be clearly stated alongside the price.
-
Family Sharing disclosure (Guideline 3.1.2(d))
Grep pattern isFamilyShareable|familyShared|familySharable in **/*.swift.
If Family Sharing is enabled on any subscription product: verify a disclosure is present on the paywall such as "Shareable with up to 5 family members" โ ๐ก MEDIUM if absent. Guideline 3.1.2(d) requires communicating Family Sharing eligibility to users before purchase.
Phase 3: Output
Collect all findings from Phase 2 and build the prioritised findings list below. Include file paths and line numbers. Omit tiers with no findings.
Output Format
## Subscription Disclosure โ Findings
### ๐ด CRITICAL โ Guaranteed rejection
- [ ] TODO: Add auto-renewal disclosure text to paywall โ e.g., "Subscription automatically renews unless cancelled" โ `PaywallView.swift` โ Guideline 3.1.2(c)
- [ ] TODO: Add post-trial billing disclosure โ state what amount is charged after the free trial ends โ `SubscriptionView.swift:45` โ Guideline 3.1.2(c)
- [ ] TODO: Display actual localized price using displayPrice or localizedPrice โ price absent from paywall UI โ `PremiumView.swift` โ Guideline 3.1.2(c)
### ๐ HIGH โ Very likely rejection
- [ ] TODO: Add cancellation instructions โ e.g., "Cancel anytime in iPhone Settings > Subscriptions" โ `PaywallView.swift` โ Guideline 3.1.2(c)
- [ ] TODO: Show actual monthly price alongside "50% OFF" discount label โ discount without price is a rejection pattern โ `PaywallView.swift:88` โ Guideline 3.1.2(c)
- [ ] TODO: Display billing period (monthly/annually/weekly) next to the price โ `SubscriptionView.swift:34` โ Guideline 3.1.2(c)
### ๐ก MEDIUM โ Possible rejection
- [ ] TODO: Verify disclosure text is visually conspicuous โ not in small gray font below the fold โ manual UI review required
### ๐ข LOW โ Best practice
- [ ] TODO: Follow Apple's subscription UI guidelines โ use clear, high-contrast disclosure text near the CTA button, not in footnotes
Tools Used
Glob, Grep, Read
Constraints
- Read-only. No file edits.
- No network calls.
- Skip Phase 1 if
shared_context is provided by orchestrating agent.
- Works on Swift, Objective-C, React Native, Flutter projects.
Quick Commands
Run these in your project root to check manually:
!grep -rn "auto-renew\|automatically renew\|renews automatically" . --include="*.swift" -i
!grep -rn "after.*trial\|trial ends\|then.*per\|free trial" . --include="*.swift" -i
!grep -rn "displayPrice\|localizedPrice\|priceLocale\|price\b" . --include="*.swift" | grep -v "//"
!grep -rn "% off\|save.*%\|discount" . --include="*.swift" -i
Swift Anti-Pattern Reference
examples/swift/SubscriptionPatterns.swift
Detection Steps
-
Find target files
- Glob:
**/*.swift, **/*.m, **/*.storyboard, **/*.xib, **/*.strings
-
Search for rejection patterns
- Grep
freeTrialPeriod\|trialPeriod\|free.*[Tt]rial\|freeTrial โ trial UI elements
- Grep
subscriptionPrice\|priceLocale\|localizedPrice\|displayPrice โ price display
- Grep
autoRenew\|auto-renew\|automatically renews\|autoRenewing โ renewal disclosure text
- Grep
%.*off\|[Dd]iscount\|[Ss]ave.*% โ discount claims
-
Determine verdict
- Trial UI found + no
autoRenew/automatically renews text near it โ ๐ HIGH (Guideline 3.1.2(c))
- Discount percentage shown + no actual billed amount displayed โ ๐ HIGH
localizedPrice displayed + renewal terms present โ ๐ข pass
-
Report
- File path + line of trial/discount UI without disclosure
- Fix: Add "Then [price]/[period], cancel anytime" text directly below the CTA; show actual price alongside any discount percentage