ayonto  0%
digital solutions

Subscriptions & payments · 3 min read

Combining Stripe subscriptions and in-app purchases

If you sell subscriptions on the web and in your iOS app, you have two checkouts: Stripe and Apple's in-app purchase. Customers still expect one account. At LeasePilot, every purchase therefore lands in the backend as a subscription with channel, tier, status and expiry date, and the backend calculates the valid tier, not the app.

Tassilo

October 8, 2026 · AuthorTassilo

No sales in the app, no in-app purchase

An iOS app that unlocks features for payment has to use Apple's in-app purchase (guideline 3.1.1). A free companion app for a paid web service gets by without it, as long as it neither sells anything itself nor points to a purchase outside the app (guideline 3.1.3(f)).

BKF Manager and Tactio take this simpler route. It fits when customers book in the browser anyway, as they often do with business software. Winning private customers directly in the app usually takes in-app purchase.

LeasePilot has both: the Plus and Pro tiers are sold on the web via Stripe and in the iPhone app via the App Store. The app may unlock web subscriptions because they're also available as an in-app purchase (guideline 3.1.3(b)). Two checkouts, then, but one account.

One account, two ways to pay

In the backend, every subscription has a channel (App Store or Stripe), a tier (Plus or Pro), a status and an expiry date. After every purchase, renewal and expiry, the backend recalculates the account's tier: the highest valid subscription counts, regardless of the channel.

A subscription also stays valid while Stripe retries a failed payment (invoice.payment_failed). The API checks limits such as the number of vehicles against the database, not against the login token. So a new tier applies immediately, without signing in again.

The app buys, the backend decides

With StoreKit 2, every transaction is signed by Apple. The app sends it to the backend, which verifies the signature and assigns the purchase to the account. Only then does the app finish it:

func buy(_ product: Product) async throws {
    guard case .success(let result) = try await product.purchase(),
          case .verified(let transaction) = result else { return }
    try await backend.confirm(signedTransaction: result.jwsRepresentation)
    await transaction.finish()  // only after the backend has confirmed
}
Simplified: purchase with StoreKit 2, finished only once the backend confirms (Swift)

Until then, StoreKit redelivers the unfinished transaction on the next launch, according to Apple only once, right after launch. So the listener on Transaction.updates belongs at app launch.

The backend learns about renewals and cancellations directly, via App Store Server Notifications (version 2) and Stripe webhooks. It only sets a status once the signature checks out. It finds Apple's notifications via the originalTransactionId, which is why the signed-in app reports the initial purchase, and a purchase belongs to exactly one account. On the web, the account ID in the Stripe Checkout metadata plays that role.

The payment screen needs links, not web prices

For subscriptions, Apple mainly checks whether customers know what they're getting before subscribing. One LeasePilot version failed right there: the link to the terms of use was missing on the payment screen (guideline 3.1.2(c)). Since then it links the terms and the privacy policy.

Prices may differ, and Apple keeps a commission on purchases in the store. That's why LeasePilot's pricing page notes that prices in the Apple App Store may differ. The app only shows the prices from StoreKit.

An app may only point to cheaper web prices in the US storefront and in the EU under Apple's separate terms, where a commission applies too. Our apps don't do this.

What we take away

Unlocking happens in the backend. The app buys and reports, the backend does the math. Google Play Billing would be one more channel there, following the same pattern.

Check the simpler route first. If the app doesn't sell anything itself, in-app purchase and the second checkout go away. Apple's other objections to our submissions are covered in Publishing an app on the App Store and Google Play.

Sources

About the author

Tassilo

Founder & CEO at ayonto, responsible for development as well as sales and customer contact.

In practice

  • LeasePilot – Keep your leasing mileage in view — forecast with a traffic light until the end of the contract, automatic odometer readings for Tesla and subscription billing via the App Store or Stripe.
  • BKF Manager – Complete digital solution for Module 95 & accelerated initial qualification (BGQ) — from online booking and QR check-in to the learning portal, invoicing and the KBA-compliant XML export.
  • Tactio – Multi-tenant SaaS for tactical simulation events — airsoft, paintball and MilSim with a live situation picture, our own vector maps, a scoring engine and on-site verification.

FAQ

Quick answers

Quick answers on “Combining Stripe subscriptions and in-app purchases”.

Does my iOS app have to offer in-app purchases?

If it unlocks features for payment, yes (guideline 3.1.1). The exception is free companion apps for a paid web service that neither sell anything themselves nor point to a purchase outside the app (guideline 3.1.3(f)).

Can a customer subscribe in the app and on the web at the same time?

Technically yes, each channel bills separately. At LeasePilot, the higher of the two subscriptions then applies.

Guides

Keep reading

TassiloRicardo

Questions about your project?

Tell us what you have in mind – Tassilo and Ricardo will get back to you personally.

ayonto · Berlin-Lichtenberg