Guideline 3.1.2 — subscriptions
Guideline 3.1.2 has a subjective half and an objective half, and almost all avoidable rejections come from the objective one.
The objective half is a checklist the paywall either satisfies or does not: the subscription title, its length, the price, functional links to terms of use and privacy policy, unambiguous auto-renewal terms, and a way to restore purchases. Every item there is testable before you submit.
The subjective half — whether the presentation is misleading, whether a trial's terms are clear enough — is a human judgement and no tool predicts it.
The most common React Native failure is not in the guideline at all: hard-coded prices. Prices vary by storefront and change over time. Read the localised price from StoreKit and display that.
The paywall checklist
Every one of these is checkable before you build.
| Requirement | Common failure |
|---|---|
| Subscription title | Generic "Premium" with no indication of what it unlocks |
| Length of the subscription period | "$59.99" with no "per year" anywhere near it |
| Price, localised | Hard-coded USD shown to a UK user |
| Price per unit where relevant | Monthly-equivalent claimed but not computed from the real price |
| Terms of use link | Missing, or pointing at a 404 |
| Privacy policy link | Missing, or pointing at a 404 |
| Auto-renewal stated plainly | Buried below the fold or in grey 9pt text |
| Restore purchases | Absent — a routine rejection |
| Trial terms, if there is a trial | Length and what happens at the end not both stated |
Test both links on a device, in the built app, before submitting. A privacy-policy URL that resolves in your browser and 404s from the app — because of a redirect, a trailing slash, or a staging domain — is a real and common cause.
The React Native and Expo specifics
Read prices from StoreKit, never from a constant. If you use RevenueCat, that is what Offering and Package give you, already localised. A hard-coded string is wrong the moment a price changes or a user opens the app outside your storefront — and a paywall showing a different number from the purchase sheet is exactly what "misleading" means here.
Implement restore, and test it. On a second device, with a fresh install, using a sandbox account that already owns the subscription. It is the step everyone skips and the one reviewers actually perform.
Keep the paywall reachable without an account where you can. A paywall behind a signup wall runs into Guideline 5.1.1(v) territory as well, and a reviewer who cannot reach it cannot evaluate it.
Make sure your metadata matches. A description advertising a three-day free trial when the app has no trial is a metadata accuracy problem on top of a 3.1.2 one — and it is a contradiction that survives in listings long after the product decision changed. Check the live description against the shipped paywall before every submission.
Your app's own subscription must use in-app purchase. Real-world services rendered offline do not require IAP — a booking app taking customer payments through Stripe for an actual service is fine. But your subscription to the app is a digital good and must go through StoreKit. Mixing those up is a 3.1.1 rejection.
What is genuinely subjective
Whether a paywall's presentation is misleading. Whether a trial's terms are prominent enough. Whether a "continue" button that starts a subscription is clear about what it starts. Whether a dismissal control is findable.
These are judgement, and reviewers are not consistent about them because people are not. The practical defence is conservatism: make the terms boringly explicit, put them above the fold, and make the price and period impossible to misread. A paywall nobody could misunderstand does not need a reviewer to be generous.
What a pre-submission scan catches
The objective half. Specifically: whether the paywall screen contains links to terms and privacy, whether those URLs resolve, whether prices are hard-coded rather than read from StoreKit, whether a restore-purchases path exists, whether the App Store description mentions a trial the code does not implement, and whether IAP is used for the app's own subscription.
shipcheck runs those locally, before you build, inside the Claude Code session you already have open. It does not — and cannot — tell you whether a reviewer will find your paywall misleading. What it checks.
Questions and answers
What must an app show on a subscription paywall?
Apple expects the subscription title, its length, the price and the price per unit where relevant, and functional links to your terms of use and privacy policy, all clearly presented before purchase. Auto-renewing terms should be unambiguous rather than buried.
Why was my app rejected under Guideline 3.1.2?
Common causes are a paywall missing the terms of use or privacy policy links, a free trial whose conversion terms are not stated clearly, prices hard-coded rather than read from StoreKit, a missing restore-purchases control, or metadata describing a trial the app does not offer.
Do I need a restore purchases button?
Yes in practice. An app selling non-consumable or subscription content needs a way for a user to restore an existing entitlement on a new device, and its absence is a routine rejection.
Can I hard-code subscription prices in my app?
No. Prices vary by storefront and change, and hard-coded prices go stale or display the wrong currency. Read the localised price from StoreKit and display that, which is also what avoids a mismatch between your UI and the actual charge.
Does my App Store description need to match my paywall?
Yes. A description advertising a free trial the app does not offer, or a price the app does not charge, is a metadata accuracy problem in addition to a 3.1.2 problem, and it is one of the easiest rejections to avoid.
shipcheck. “Guideline 3.1.2 — subscriptions.” Baker Ventures LLC, September 6, 2026. https://shipcheck.bakerventuresstudio.com/rejections/guideline-3-1-2-subscriptions/