Now While submitting the app for App review on App Store, I got all these things. Now help me with this. So basically, is there something that I can change in my app or you know, I can just, you know, send them a reply with a proper reason why it should be accepted and things like that or give me some kind of solution. So basically the thing is that this is a digital app, digital app, right? And we offer subscriptions that the users cannot buy through the app, but they have to buy it through the website, right? But after buying the subscription from the website, they can use it inside the app, get the experience inside the app. We want to do a global release, at least in US and India, at least. And the thing is that we don't want to integrate in-app purchases because that is too tricky. We use Stripe and we offer different kind of subscription models, upgrade, degrade, downgrade, etc. etc. So we don't want to get into that. Right, so like managing the subscription differently for Stripe and differently for this in-app purchases. So we don't want to do that. We don't want to get into it. So tell me some solution, what can be done and like what is the closest that we can get and how to handle the situation.
Apple App Store guideline 3.1.1 external payment link US storefront 2025 2026 reader app exception
Apple guideline 3.1.3(b) multiplatform services app external purchase exception
Apple App Store India storefront external payment link guideline 3.1.1 allowed 2025
| Storefront | External payment links allowed? | IAP required? |
|---|---|---|
| United States | ✅ Yes — no entitlement needed (post-Epic ruling, May 2025) | No, if you link out to your website |
| India | ❌ No — external link exception does NOT apply | Yes, IAP required for digital content |
SKPaymentQueue.storefront and show the right flowThank you for the detailed feedback.We understand the requirements under Guideline 3.1.1. We'd like to clarify our model and proposed resolution:Our app is a multi-platform digital service. Subscriptions are currently purchased on our website. We plan to add an external link button in the app (linking to our website's subscription page) for the United States storefront, in accordance with the updated Guideline 3.1.1(a) which now permits external payment links on the US storefront without a special entitlement.For non-US storefronts (such as India), we are actively implementing In-App Purchase via StoreKit so that the same subscriptions are available to purchase within the app, per Guideline 3.1.3(b).We request a short extension to implement these changes and resubmit. We are committed to full compliance.Thank you for your patience.
Storefront.current (StoreKit 2) to show IAP UI to Indian users and external link to US users. This is standard practice.Try again
Apple App Store avoid in-app purchase SaaS B2B enterprise app approved without IAP 2025
Apple guideline 3.1.3(c) enterprise services B2B app no IAP required App Store
Apple App Store "reader app" exemption digital subscription no IAP SaaS tool approved 2025
Apple App Store free app no paid features IAP workaround approved "website only" subscription 2025
| Exception | Who it covers | Does it fit you? |
|---|---|---|
| Reader apps (3.1.3a) | Magazines, newspapers, books, audio, music, video — primary purpose is consumption of that content type | Only if Orris is fundamentally a media reader |
| Enterprise services (3.1.3c) | App sold directly only to organisations (B2B, not consumer-facing) | Only if you sell exclusively to companies, not individual users |
| Person-to-person (3.1.3d) | Live 1:1 services like tutoring or personal training | Unlikely for a SaaS tool |
I am interested in this option 1 tell me more about this in detail
Apple App Store approved SaaS app no IAP free tier subscription unlocked server side login 2025
Apple App Review notes explain no in-app purchase subscription web only app approved examples
"This feature is available on Pro. Visit orris.app to upgrade."
plan: "pro" or similarApp Review Notes — OrrisOrris is a [describe what your app does] platform available on web and mobile. The app is free to download and includes a free tier that allows all users to [describe what free users can do — e.g., "create up to 3 projects, send up to 50 messages per month, etc."].Subscriptions are managed exclusively on our website (orris.app) and are not sold within the app. When a subscribed user logs in, their subscription status is verified via our backend API and premium features are unlocked automatically — no purchase flow occurs inside the app.Non-subscribed users are shown informational text directing them to our website if they wish to upgrade, but no payment links, buttons, or external purchase calls-to-action are present within the app.This model is consistent with how multi-platform SaaS tools operate on the App Store.Demo account for review: Email: review@orris.app Password: [password] (This is a pre-subscribed Pro account so you can review all features.)
Thank you for the detailed feedback regarding Guideline 3.1.1.We'd like to clarify our app's business model:Orris is a multi-platform digital service. Our app offers a genuine free tier available to all users upon signup. Subscriptions are purchased exclusively on our website — no in-app purchase, payment link, or external call-to-action for purchasing exists within the app on non-US storefronts.When a user who has purchased a subscription on our website logs into the app, their subscription status is validated server-side and features are unlocked automatically. This is not an in-app purchase — it is simply a server-side entitlement check.We are updating the app to ensure:
- A meaningful free tier is clearly accessible to all users without requiring a subscription
- No payment links or calls-to-action appear inside the app (except on the US storefront, per the updated Guideline 3.1.1(a))
- App Review Notes clearly explain the model with a pre-subscribed demo account
We are resubmitting with these changes shortly and appreciate your patience.
| What | Action |
|---|---|
| Subscription/pricing screen | Remove entirely from the app (for non-US builds, or use a geo-check) |
| "Upgrade" buttons anywhere in app | Replace with plain text: "Visit orris.app to manage your plan" — no tappable link |
| Locked feature screens | Show what the feature is + plain text about the plan needed, not a purchase prompt |
| Login/signup flow | Must work freely — no paywall at the door |
| Free tier features | Make them real and usable — define this clearly |
| Backend API | Ensure your subscription status endpoint is reliable — app unlocks based on this |
| US storefront (optional) | You can add a "Subscribe" button that opens Safari for US users only — detect storefront via Storefront.current in StoreKit |
// StoreKit 2
if let storefront = await Storefront.current, storefront.countryCode == "USA" {
// Show "Subscribe" button linking to your Stripe page
} else {
// Show plain text only: "Visit orris.app to manage your plan"
}
| Risk | Mitigation |
|---|---|
| Reviewer finds the app useless on free tier | Build a real, usable free tier before resubmitting |
| Reviewer sees "locked" screens and flags them | Replace lock screens with feature previews + plain informational text |
| Apple changes interpretation later | You're on solid ground if no purchase/link exists in the app |
| Free tier users never convert | Push conversion via email, web notifications, not the app |
I am interested in this option 1 tell me more about this in detail
User logs in to app
↓
App calls your backend: GET /api/me/subscription
↓
Backend checks Stripe → returns { plan: "free" | "pro" | "enterprise", status: "active" | "inactive" }
↓
App stores this in state/context
↓
Every feature check reads from this state — no purchase, no IAP, no Apple involved
# routes/subscription.py
from fastapi import APIRouter, Depends
from app.auth import get_current_user
from app.services.stripe_service import get_subscription_status
router = APIRouter()
@router.get("/api/me/subscription")
async def subscription_status(current_user=Depends(get_current_user)):
status = await get_subscription_status(current_user)
return status
# services/stripe_service.py
import stripe
from app.config import STRIPE_SECRET_KEY
stripe.api_key = STRIPE_SECRET_KEY
async def get_subscription_status(user):
# If user has no stripe customer ID, they're on free
if not user.stripe_customer_id:
return {
"plan": "free",
"status": "active",
"features": get_features_for_plan("free")
}
# Fetch active subscriptions from Stripe
subscriptions = stripe.Subscription.list(
customer=user.stripe_customer_id,
status="active",
limit=1
)
if not subscriptions.data:
return {
"plan": "free",
"status": "active",
"features": get_features_for_plan("free")
}
sub = subscriptions.data[0]
plan_name = sub.metadata.get("plan", "pro") # set this in Stripe metadata
return {
"plan": plan_name,
"status": sub.status, # "active", "trialing", "past_due"
"current_period_end": sub.current_period_end,
"features": get_features_for_plan(plan_name)
}
def get_features_for_plan(plan: str) -> dict:
plans = {
"free": {
"max_projects": 3,
"max_team_members": 1,
"can_export": False,
"can_use_api": False,
"advanced_analytics": False,
},
"pro": {
"max_projects": -1, # -1 = unlimited
"max_team_members": 10,
"can_export": True,
"can_use_api": True,
"advanced_analytics": True,
},
"enterprise": {
"max_projects": -1,
"max_team_members": -1,
"can_export": True,
"can_use_api": True,
"advanced_analytics": True,
}
}
return plans.get(plan, plans["free"])
features object so the app doesn't need to know your plan logic — it just reads flags# routes/webhooks.py
from fastapi import APIRouter, Request, HTTPException
import stripe
from app.config import STRIPE_WEBHOOK_SECRET
from app.models import User
router = APIRouter()
@router.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig_header = request.headers.get("stripe-signature")
try:
event = stripe.Webhook.construct_event(
payload, sig_header, STRIPE_WEBHOOK_SECRET
)
except stripe.error.SignatureVerificationError:
raise HTTPException(status_code=400, detail="Invalid signature")
if event["type"] in [
"customer.subscription.created",
"customer.subscription.updated",
"customer.subscription.deleted",
"invoice.payment_succeeded",
"invoice.payment_failed",
]:
subscription = event["data"]["object"]
customer_id = subscription["customer"]
user = await User.get(stripe_customer_id=customer_id)
if user:
user.subscription_status = subscription["status"]
user.subscription_plan = subscription.get("metadata", {}).get("plan", "pro")
user.subscription_ends_at = subscription.get("current_period_end")
await user.save()
return {"status": "ok"}
/api/me/subscription endpoint just reads user.subscription_plan and user.subscription_status from your DB — no live Stripe call needed.// context/SubscriptionContext.js
import React, { createContext, useContext, useState, useEffect } from 'react';
import { AppState } from 'react-native';
import api from '../services/api';
const SubscriptionContext = createContext(null);
export function SubscriptionProvider({ children }) {
const [subscription, setSubscription] = useState({
plan: 'free',
status: 'active',
features: {
max_projects: 3,
max_team_members: 1,
can_export: false,
can_use_api: false,
advanced_analytics: false,
},
loading: true,
});
const fetchSubscription = async () => {
try {
const data = await api.get('/api/me/subscription');
setSubscription({ ...data, loading: false });
} catch (e) {
// On error, default to free — never crash the app
setSubscription(prev => ({ ...prev, loading: false }));
}
};
useEffect(() => {
fetchSubscription();
// Re-check when app comes back to foreground
// (user may have just subscribed on the website)
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') {
fetchSubscription();
}
});
return () => subscription.remove();
}, []);
return (
<SubscriptionContext.Provider value={{ subscription, refetch: fetchSubscription }}>
{children}
</SubscriptionContext.Provider>
);
}
export const useSubscription = () => useContext(SubscriptionContext);
AppState listener is important — when a user leaves the app, subscribes on your website, and comes back, it automatically re-checks their entitlement and unlocks features. This is the magic that makes the flow feel seamless.if checks.// components/FeatureGate.jsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useSubscription } from '../context/SubscriptionContext';
export function FeatureGate({ feature, children, fallback }) {
const { subscription } = useSubscription();
if (subscription.loading) return null;
const hasAccess = subscription.features[feature] === true
|| subscription.features[feature] === -1
|| (typeof subscription.features[feature] === 'number'
&& subscription.features[feature] > 0);
if (hasAccess) return children;
// Show fallback or default upgrade notice
return fallback || <DefaultUpgradeNotice />;
}
function DefaultUpgradeNotice() {
return (
<View style={styles.notice}>
<Text style={styles.title}>Pro Feature</Text>
<Text style={styles.body}>
This feature is available on the Pro plan.{'\n'}
Visit orris.app to upgrade.
</Text>
{/* ⚠️ NO BUTTON. NO LINK. Just text. This is intentional for App Store compliance. */}
</View>
);
}
const styles = StyleSheet.create({
notice: {
padding: 20,
backgroundColor: '#f5f5f5',
borderRadius: 12,
alignItems: 'center',
},
title: {
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
},
body: {
fontSize: 14,
color: '#666',
textAlign: 'center',
lineHeight: 22,
},
});
// In any screen
import { FeatureGate } from '../components/FeatureGate';
function AnalyticsScreen() {
return (
<FeatureGate feature="advanced_analytics">
<AnalyticsDashboard />
</FeatureGate>
);
}
// hooks/useStorefront.js
import { useState, useEffect } from 'react';
import { Platform, NativeModules } from 'react-native';
export function useIsUSStorefront() {
const [isUS, setIsUS] = useState(false);
useEffect(() => {
if (Platform.OS !== 'ios') return;
// Get device locale/region as a proxy
// For more precision, use a native module to read SKStorefront
const locale = NativeModules.SettingsManager?.settings?.AppleLocale
|| NativeModules.SettingsManager?.settings?.AppleLanguages?.[0]
|| '';
// Or simply check from your backend — pass the storefront in the subscription response
setIsUS(locale.includes('US') || locale.includes('en_US'));
}, []);
return isUS;
}
function DefaultUpgradeNotice() {
const isUS = useIsUSStorefront();
return (
<View style={styles.notice}>
<Text style={styles.title}>Pro Feature</Text>
<Text style={styles.body}>
This feature is available on the Pro plan.
</Text>
{isUS ? (
// US: allowed to show external link
<TouchableOpacity onPress={() => Linking.openURL('https://orris.app/pricing')}>
<Text style={styles.link}>Subscribe at orris.app →</Text>
</TouchableOpacity>
) : (
// Non-US (India etc.): plain text only, no link
<Text style={styles.body}>Visit orris.app to upgrade.</Text>
)}
</View>
);
}
Thank you for your review and feedback on Guideline 3.1.1.We'd like to clarify our app's model and the changes we are making:Our business model: Orris operates across web and mobile. Subscriptions are managed exclusively on our website via our web checkout. The iOS app does not sell, advertise, or link to any external purchase mechanism — it simply checks the user's subscription status from our backend at login and unlocks features accordingly.Changes we are implementing before resubmission:
Genuine free tier: All users get access to core features (up to 3 projects, basic functionality) without any subscription. The app is fully usable on the free tier. No purchase UI: Any screens referencing subscription upgrades have been removed from the app. Non-subscribed users see informational text only ("Visit orris.app to manage your plan") — no buttons, links, or calls to action to external payment on non-US storefronts. US storefront: Per the updated Guideline 3.1.1(a), US users will see an external link button to our website for subscription management, as now permitted. App Review Notes: We have updated the App Review Information section with a pre-subscribed demo account and a full explanation of our model.We are resubmitting shortly. Thank you for your patience.
Orris is a multi-platform productivity/[your category] tool available on web and iOS.Business model: The app is free to download. A free tier is available to all users with no subscription required (up to 3 projects, [list your free features]). Paid subscriptions are purchased exclusively on our website (orris.app) using Stripe. No in-app purchases, external payment links, or purchase calls-to-action exist within the app on non-US storefronts.How premium features unlock: When a subscribed user logs in, the app calls our backend API which checks their Stripe subscription status and returns a feature entitlement object. The app unlocks features based on this response. No payment processing occurs on-device.US storefront: An external link to our website is shown to US users per Guideline 3.1.1(a).Demo credentials (pre-subscribed Pro account): Email: review@orris.app Password: ReviewOrris2026!Please use these credentials to access and review all Pro features.
/api/me/subscription endpoint returns plan + feature flagsAppState listener re-fetches entitlement when app resumes from backgroundAppState change → re-fetches entitlement → features unlock automaticallyLook i am not able to understand any of this I dont want to get into technicalities but what i am trying to understand is that you are just saying me to remove subscription page from my UI right now so that it does not redirect thats it right?
Ok now listen one thing i will write a reply to apple that our app is completely free to use by all users and there is no restriction or paid tier and billing button which is a redirect link and genuinely keep the app free to use for 1 week till the time our app does not gets approved on app store Then this image shows our app right now from which i will remove limits which shows here and the billing button as well Then add some sort of webview component here like a button or section in the screen or division or something which shows something generic like about us or blank or whatever and when the app gets approved we will update the website in that web-view link (not the app) to limits and billing section which are the same 2 things that we removed right now from the app Isnt that the best plan And we are very small so apple will not even notice us We are tiny
"Don't use a WebView to load content that mimics App Store features or payment flows."
"Apps should not be submitted that are intended to deceive the reviewer."
| What | Action |
|---|---|
| Limits & Usage section | Remove it |
| Billing button | Remove it |
| App functionality | Keep it fully working, no paywalls |
| Reply to Apple | Say the app is free with no billing in the app — which will be true |
| Future billing for iOS users | Handle via your website, users manage plans there |
Ok i agree with rhis plan you are suggesting and i will go forward with rhis and no free tier all the things as it is jist remove that 2 things from app So write me the reply that i neew ro give, changes that i need to make and hiw to seprate it for us vs rest of the world users guide me not in coding just the general guidance
Thank you for your continued review of Orris and for the detailed feedback.We have reviewed Guideline 3.1.1 carefully and want to clarify the changes we are making before resubmission:What we are changing:
We are removing the "Limits & Usage" section from the app's Settings screen entirely. We are removing the "Billing" button from the Settings screen. For users on the United States storefront, we will replace this with an external link to our website, which is permitted under the updated Guideline 3.1.1(a). For all other storefronts, no billing link or button will be present in the app.Our model going forward:Orris does not sell digital content or subscriptions within the app. Subscriptions are managed exclusively on our website. The app itself serves as a companion interface — when a user who has an active subscription logs in, their access is determined server-side. No purchase flow, payment link, or external call-to-action for purchasing exists within the app on non-US storefronts.We are resubmitting with these changes shortly. Please find demo credentials below to access the app during review:Demo account: Email: [your review account email] Password: [your review account password]Thank you for your patience and we look forward to a successful review.Regards, [Your name] Orris
Orris is a multi-platform AI tool available on web and iOS. The app does not contain any in-app purchases, billing screens, or payment links (except an external website link for US storefront users per Guideline 3.1.1a).Subscriptions are purchased and managed entirely on our website. The app checks subscription status from our backend at login and provides access accordingly.Demo account (active subscription): Email: [your review account email] Password: [your review account password]