"Restore Purchases" button accessible and functional
Why it matters
Apple guideline 3.1.1 explicitly requires a "Restore Purchases" mechanism for all non-consumable IAP and subscriptions. Its absence is a rejection reason — but the practical impact compounds post-launch: users who delete and reinstall the app, upgrade to a new device, or restore from backup lose access to content they paid for. These users generate support tickets, file chargebacks with their bank or credit card company, and leave one-star reviews. Chargebacks damage the developer's payment processor standing even though Apple actually processed the payment. A functional restore path prevents all of this at minimal implementation cost.
Severity rationale
Critical because Apple guideline 3.1.1 explicitly mandates a restore mechanism, and its absence causes immediate rejection — not a warning.
Remediation
Add a "Restore Purchases" button to your paywall and/or settings screen. The button must be reachable without completing a purchase — users who reinstalled the app have no other path to reclaim their access.
// PaywallScreen.tsx or SettingsScreen.tsx
const handleRestore = async () => {
try {
const customerInfo = await Purchases.restorePurchases();
if (customerInfo.entitlements.active['premium']) {
unlockPremium();
Alert.alert('Purchases Restored', 'Your subscription has been restored.');
} else {
Alert.alert('No Purchases Found', 'No active subscription was found for this account.');
}
} catch (e) {
Alert.alert('Restore Failed', 'Could not restore purchases. Please try again.');
}
};
<TouchableOpacity onPress={handleRestore}>
<Text>Restore Purchases</Text>
</TouchableOpacity>
Place the restore button in both the paywall and the settings/account screen. Verify that after restore, the entitlement state is refreshed and the premium UI unlocks immediately.
Detection
- ID:
restore-purchases - Severity:
critical - What to look for: Count all relevant instances and enumerate each. Search for restore-related calls in source files:
Purchases.restorePurchases()(RevenueCat),restoreCompletedTransactions()(StoreKit 1),AppStore.sync()(StoreKit 2),restorePurchases()(react-native-iap),InAppPurchase.completePurchase()with restore flag (Flutter),BillingClient.queryPurchasesAsync()for query-based restore (Play Billing). Find where this function is called — trace back to the UI element that triggers it. Verify: (1) A "Restore Purchases" button exists and is rendered. (2) It is accessible on the paywall OR in a settings/account screen reachable without completing a purchase (users who deleted and reinstalled the app cannot make a new purchase to access settings). (3) The button is visible — not hidden behind a menu with no visual affordance. (4) After restore, entitlements are refreshed and premium UI is unlocked if applicable. Fail pattern: restore logic exists in code but the button is commented out, behind a__DEV__guard, or inside a screen only accessible after purchasing. - Pass criteria: A "Restore Purchases" button (or equivalent link) is present in the paywall and/or settings screen, is accessible to logged-in and guest users alike, and calls the platform's restore API which then updates the local entitlement state. At least 1 implementation must be verified.
- Fail criteria: No restore button visible in paywall or settings; restore button exists but is unreachable without first purchasing; restore call is stubbed or does nothing after triggering; restore button hidden behind a debug flag.
- Skip (N/A) when: No IAP detected in the app.
- Detail on fail:
"No 'Restore Purchases' button found in PaywallScreen.tsx or Settings — Apple guideline 3.1.1 requires restore to be available"or"restorePurchases() is called in Settings but Settings screen is only accessible after subscribing — new users who reinstalled the app cannot restore" - Remediation: Apple explicitly requires a "Restore Purchases" mechanism for all non-consumable and subscription IAP. Absence causes rejection under guideline 3.1.1.
- Add a "Restore Purchases" button to your paywall:
<TouchableOpacity onPress={handleRestore}> <Text style={styles.restoreText}>Restore Purchases</Text> </TouchableOpacity> - For RevenueCat:
const handleRestore = async () => { try { const customerInfo = await Purchases.restorePurchases(); if (customerInfo.entitlements.active['premium']) { unlockPremium(); Alert.alert('Purchases Restored', 'Your subscription has been restored.'); } else { Alert.alert('No Purchases Found', 'No active subscription was found for this account.'); } } catch (e) { Alert.alert('Restore Failed', 'Could not restore purchases. Please try again.'); } }; - Place the restore button in both the paywall and the settings/account screen for discoverability
- Add a "Restore Purchases" button to your paywall:
External references
- external · apple-guideline-3.1.1 — Apple App Store Review Guidelines § 3.1.1 — In-App Purchase (restore purchases requirement)
Taxons
History
- 2026-04-18·v1.0.0·Initial import from app-store-iap-subscriptions·automated