Paywall  /  Store

Store

Your Store is a hosted pricing page — one card per plan, styled under Store page and Branding. The web way to show it is to share its URL or embed it as an iframe — no SDK. Native apps can show it in-app (see below) — with one important exception on iOS and the Mac App Store, where Apple requires a different shape.

Show the store on your website

Every app has a hosted store at a public URL. Share it directly, or embed it — these are the exact snippets your dashboard's SDK → Store tab generates (same source, pre-filled with your app's slug). Replace YOUR_APP_SLUG with your app's slug.

Hosted store page — share this URL

url
https://onelo.tools/customer/app/YOUR_APP_SLUG

Embed · Full-width (follows browser width — for the multi-column desktop store)

html
<iframe
  id="onelo-embed-store-YOUR_APP_SLUG"
  src="https://onelo.tools/customer/app/YOUR_APP_SLUG"
  style="width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);border:none;min-height:480px;"
  loading="lazy"
></iframe>
<script>
  // Auto-resize the iframe height to its content (no inner scrollbar).
  window.addEventListener('message', function (e) {
    if (e.origin !== "https://onelo.tools") return;
    var d = e.data || {};
    if (d.type === 'onelo:resize' && d.height) {
      var f = document.getElementById('onelo-embed-store-YOUR_APP_SLUG');
      if (f) f.style.height = d.height + 'px';
    }
  });
</script>

Embed · In-container (fills its slot — keeps the store inside your page column)

html
<iframe
  id="onelo-embed-store-YOUR_APP_SLUG"
  src="https://onelo.tools/customer/app/YOUR_APP_SLUG"
  style="width:100%;border:none;min-height:480px;"
  loading="lazy"
></iframe>
<script>
  // Auto-resize the iframe height to its content (no inner scrollbar).
  window.addEventListener('message', function (e) {
    if (e.origin !== "https://onelo.tools") return;
    var d = e.data || {};
    if (d.type === 'onelo:resize' && d.height) {
      var f = document.getElementById('onelo-embed-store-YOUR_APP_SLUG');
      if (f) f.style.height = d.height + 'px';
    }
  });
</script>
Both embeds carry a tiny postMessage listener that auto-resizes the iframe to its content (no inner scrollbar). The store adapts on its own: it shows your plans normally, and while Waitlist mode is on it redirects visitors to your waitlist (invited users with a token go straight to checkout).
Displaying the store vs selling from it are two different bars. The store page renders publicly (from your active products) — gated only by your Onelo plan's Store capability and by Waitlist mode. But to actually check out, two more things must be true: a connected Stripe account and the Access Gate → “Plan required” turned ON (Paywall → Store). With the gate off the store still shows, but the buy link /buy/… returns 404 — an app with the paywall off is free, so there's nothing to buy.

Show the store in a native app

In a native app (Swift, Android, Flutter, React Native, Electron) you don't embed an iframe — the store shows in-app, driven by Auth. Turn on Paywall → Store → Access Gate → “Plan required”, and the Onelo sign-in view (loadAuthView() / OneloAuthView) routes sign-up → store automatically: any user without an active plan buys before they reach the app. The Stripe card page hands off to the system browser. Set this up on the Auth tab — there's nothing extra to call.

Unlike the website embed, this in-app flow requires “Require plan on sign-up” to be ON, plus at least one visible store option — that toggle is what makes the native sign-up route to the store.

On iOS and the Mac App Store, Onelo closes this flow by default.

Every route that ends at the store is refused for those platforms unless you say otherwise: sign-in and sign-up stop short of it, store-initiate and upgrade-initiate answer 409 in_app_store_not_allowed, and both the customer portal and the manage page hide “Change plan” and “View plans”. Cancelling, invoices and refunds keep working everywhere — those are your buyer's own interests, and we never block them.

In your own code, openUpgrade() becomes a no-op that logs the reason, and openStore() / initiateStoreFlow() raise in_app_store_not_allowed rather than opening a WebView — so catch it wherever you already handle store errors.

The switch is Paywall → Store → Access Gate, on the app itself. It is set per platform, so your iOS app can be closed while your web app sells normally — but note that “Sign in only” is not an iOS-only mode: whichever app you set it on stops selling in-app entirely. Choosing “Show the store” hands you responsibility for App Review. Full behaviour on the Access Gate reference.

Apple App Store: this flow needs a different shape

The in-app store above is fine on macOS distributed outside the Mac App Store, on Windows and Linux, and on the web — Apple's rules don't reach those.

On iOS and the Mac App Store, App Review guideline 3.1.1 requires purchases of digital content to go through Apple's own in-app purchase. Showing prices and a buy flow inside the app is not permitted, and “Plan required” will put you there.

The shape that works everywhere: sell on your website, and let the app sign the buyer in. Your website keeps the Stripe checkout, the VAT handling and the invoice; the app carries sign-in only — no prices, no buy button, no link to your pricing page. The purchase is recognised the moment they sign in, because it's the same Onelo account.

Two regional exceptions, both narrower than they look. On the US storefront, linking out to your own site is currently allowed with no entitlement and no Apple commission — but that is a court order under appeal, not Apple policy, so don't build on it permanently. In the EU, linking out is allowed with the StoreKit External Purchase Link Entitlement, but Apple still charges roughly 17–20%, which is more than the 15% Small Business Program rate for in-app purchase — so for most small developers it isn't worth it.

Not permitted at all on iOS: linking out from the app to buy in the UK, Canada, Australia and every other storefront.

If your app is entirely behind a paid plan, also give it a working free tier — App Review rejects apps that do nothing until a purchase it cannot see.

The shape we recommend for iOS: one product, two apps

Coming soon. Umbrella apps exist in the dashboard today (you can see and manage an existing one), but creating a new umbrella app or attaching a new app to one is temporarily disabled while we finish the feature. The shape below is what to plan for — not yet something you can set up.

Create an umbrella app in Applications, then attach two apps to it: your iOS or macOS app, and a web app. They share one user base, one store and one set of plans, so a purchase made on the website is recognised the moment the buyer signs into the app — same account, same password, nothing to sync.

This is what makes the compliant shape practical rather than painful. The website does the selling, the VAT and the invoice. The app does sign-in. And the parts your buyer is entitled to — cancelling, changing plan, downloading invoices, asking for a refund — live in the customer portal on the website, so your iOS app never has to offer them, or link to them.

Set the Access Gate per app: “Sign in only” on the iOS child, and leave the web child selling normally. Where buying is allowed lives on each app's own Paywall tab, never on the umbrella app — the umbrella app lists its attached apps so you choose for each one. “Plan required” is the opposite: it belongs to the shared identity, so you set it once on the umbrella app.

What to say in the app when there is no plan

Guideline 3.1.3 forbids steering a user to buy outside Apple's in-app purchase — no link, no price, no “get it on our website”, not even in passing. What is allowed is stating a fact. Two situations, two different sentences; copy them as they are.

They have a subscription and want to manage it — bought anywhere, since the account is shared:

txt
This subscription can't be changed in this app because it was
purchased elsewhere. Sign in to your account where you made
the purchase.

They have no plan at all. This is the state Onelo hands you after a successful sign-in on an Apple platform — a valid session and an empty entitlement — so gate your paid UI on entitlement and show this where the paid part would have been.

txt
A plan is required to use this app. Plans aren't available for
purchase in this app.

The second message is a dead end for the user, and we would rather say so than pretend otherwise. There is no wording that fixes it, because the thing that would help — telling them where to buy — is exactly what the guideline forbids.

The way out is product, not copy: give the app a working free tier, so somebody who signs in without a plan still has an app in front of them. An app that does nothing until a purchase it can't show gets rejected under 2.3.1 anyway.

Gate paid UI on entitlement

Once a buyer has paid, gate your paid UI on their entitlement — it's active | none, not a plan string or product id on the user:

js
// Client (Swift / JS / …): snapshot of the session's entitlement — no network,
// kept fresh by the SDK's live connection (revoke pushes over SSE)
const hasAccess = await onelo.auth.hasActiveAccess()

For a server-trusted check, verify the user's token on your backend — the GET /api/sdk/auth/user response carries entitlement.

For per-feature / per-tier gating (not just paid-or-not), use the Features SDK — the backend resolves each feature against the user's real plan. Never compare plan names client-side.

Looking for the dashboard side — products, store design, customer portal, Stripe connection? See the Paywall guide.
Store — Paywall — Onelo Docs