Non-consumable IAP is a category where every mistake in purchase restoration logic turns into an App Store complaint and a chargeback. A user bought "unlimited mode" or "remove ads," reinstalled the app, and didn't get what they paid for. Apple Support won't help: restoring non-consumable purchases is the developer's responsibility. Our five years of experience and more than twenty IAP projects show that correct implementation from the first try saves up to 40% of time on bug fixes. According to the StoreKit documentation, proper restoration is critical.
What Most Often Goes Wrong
The most common mistake is calling SKPaymentQueue.default().restoreCompletedTransactions() only when the user taps the "Restore" button. Correct approach: at every launch, check originalTransaction via SKReceiptRefreshRequest or server-side validation. Without this, a user returning after six months with a new iPhone will lose access to paid content.
The second issue is incorrect handling of SKPaymentTransactionObserver. If updatedTransactions does not call finishTransaction(_:) for all states (.purchased, .restored, .failed), the transaction stays in the queue and triggers the observer again at next launch. We've seen projects where this caused double activation of paid content after a restart.
The third problem is the lack of server-side validation. Local receipt parsing via ASN.1 is complex and vulnerable: an attacker can replace the receipt file. Server verification through Apple's /verifyReceipt endpoint (or alternative) guarantees authenticity.
How a Correct Implementation Works
The architecture of non-consumable IAP centers around StoreKit 2 (iOS 15+) or StoreKit 1 with support for iOS 13–14. Compare the two approaches:
| Aspect |
StoreKit 2 |
StoreKit 1 (Legacy) |
| Minimum iOS version |
15.0 |
3.0 |
| API style |
async/await |
Delegate + completion handlers |
| Purchase restoration |
Automatic via Transaction.currentEntitlements |
Manual restoreCompletedTransactions() |
| Signature verification |
Built-in VerificationResult |
Requires ReceiptValidator |
| Code complexity |
Low |
Medium |
StoreKit 2 radically simplifies the code: it reduces the number of lines by 2 times compared to StoreKit 1.
// Request products
let products = try await Product.products(for: ["com.app.premium_unlock"])
// Purchase
let result = try await products.first?.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
// unlock content
await transaction.finish()
case .unverified:
// receipt is forged — do not unlock
break
}
case .pending:
// SCA or parental control — wait
break
case .userCancelled:
break
}
Transaction.currentEntitlements is an async sequence that returns all active purchases at each app launch. Iterate it in @main or AppDelegate.applicationDidFinishLaunching and restore state without a "Restore" button.
For iOS 13–14, StoreKit 1 with SKPaymentTransactionObserver remains. That requires a separate ReceiptValidator — either local verification via openssl (complex but without network requests) or server-side through Apple's /verifyReceipt endpoint (deprecated but works). We recommend server-side: local requires embedding the Apple root certificate and correct ASN.1 parsing.
How to Avoid Purchase Restoration Problems?
Key point: perform restoration automatically at every launch, not only via a button. In StoreKit 2, this is achieved by subscribing to Transaction.updates. In StoreKit 1, call restoreCompletedTransactions() and save the originalTransaction.transactionIdentifier in UserDefaults or Keychain. Without automation, users with new devices will lose their content.
Why Is Server Verification Critical?
For apps with a backend: upon purchase, the client sends appStoreReceiptURL to the server; the server queries Apple Sandbox/Production and stores the original_transaction_id in the database. On restoration from a new device, the server verifies against the Apple ID. This is the only way to guarantee "one device purchase, access on another" under the same Apple ID. Average savings from correct implementation — up to $10,000 per year on support.
Step-by-Step Plan for Implementing StoreKit 2
- Configure products in App Store Connect (localizations, prices).
- Implement product request via
Product.products(for:).
- Add purchase handling with
VerificationResult check.
- Subscribe to
Transaction.updates for automatic restoration.
- Integrate server verification via
/verifyReceipt.
- Test in Sandbox on the scenario: purchase → delete → restore.
Typical restoration mistake
// WRONG: restoration only via button
func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
// CORRECT: automatic restoration at startup
Task {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
// unlock content
}
}
}
Work Process and What's Included
Our turnkey IAP implementation includes:
- Setting up products in App Store Connect (creation, localizations, prices — specific amounts calculated individually).
- Integrating StoreKit 2 with fallback to StoreKit 1 for maximum compatibility.
- Server-side receipt verification with storage of
original_transaction_id.
- Testing in Sandbox and on real devices with a scenario checklist.
- Documentation for support and access.
- Guarantee of passing App Review (compliance with paragraph 3.1.1 of the App Store Review Guidelines).
Testing
In Xcode Simulator, StoreKit works via a local .storekit file — you can test without real products. For device testing, a Sandbox Account in App Store Connect is required. Crucial scenario to test: purchase → delete → reinstall → restore. This path fails most often. Get a consultation on your project — we will assess risks and propose the optimal solution.
Implementation timeline — from 2 to 3 days: configure products in App Store Connect, integrate StoreKit 2 with fallback to StoreKit 1, cover with Sandbox tests, pass review (App Review requires a "Restore Purchases" button in the interface). Order turnkey IAP integration — we guarantee correct operation on all devices and iOS versions.
Mobile App Monetization: IAP, Subscriptions, and Ad Mediation
An app with poorly implemented purchases loses money not because users don't want to pay, but because a StoreKit transaction hangs, Receipt Validation fails with an error, or restore purchases doesn't work — and the user writes to support or leaves a 1-star review. Our experience (over 7 years in mobile development) shows that proper monetization increases LTV by 30–60% within the first three months after implementation. Get a consultation on monetizing your app — we'll analyze the current model and find growth points.
Why StoreKit 2 is the Best Choice for IAP?
StoreKit 2 (iOS 15+) is a modern API with async/await and device-side verified transactions without a server. Transaction.currentEntitlements returns all active purchases. Key change compared to StoreKit 1: JWS signature verification on device via VerificationResult<Transaction> — no need to send receipt to server for basic validation.
But server-side validation is still needed for consumable purchases and fraud prevention. App Store Server API replaces the old /verifyReceipt endpoint. Webhooks via App Store Server Notifications v2 provide real-time events: SUBSCRIBED, DID_RENEW, EXPIRED, REFUND — without polling.
A typical mistake: not handling paymentQueue(_:updatedTransactions:) in the background for unfinished transactions. User bought a consumable, app crashed before finishTransaction — purchase remains in queue, restores on next launch and requires reprocessing on server. Without server idempotency — double crediting.
How Not to Lose Revenue on Subscriptions?
The subscription model requires tracking states: trial → active → grace period → expired → refunded. RevenueCat is the de facto standard for managing subscriptions in production. It abstracts StoreKit and Google Play Billing, providing a unified API, webhooks, cohort analytics, and A/B testing of paywalls.
Alternatives to RevenueCat include custom implementations with Adapty or Qonversion. Fully custom only if data must not leave the infrastructure or there is non-standard logic. We guarantee that webhook setup and subscription lifecycle event handling is done without losses — verified on projects with over 500k DAU.
Google Play Billing Library 6+ requires handling PurchasesUpdatedListener and explicitly calling acknowledgePurchase() or consumePurchase() within 3 days — otherwise Google automatically cancels the purchase and refunds. The average cost of such an error is a significant loss per user per month (based on our project data).
Ad Mediation: Boosting CPM via Bidding
Showing ads from a single source means losing revenue. Mediation (waterfall or bidding) requests ads from multiple networks and displays the best bid. Google AdMob is the foundation for banner, interstitial, rewarded ads. Mediation via AdMob Mediation or MAX (AppLovin) is the second de facto standard. MAX uses In-App Bidding — a real-time auction without waterfall. In practice, MAX yields significantly higher CPM than classic waterfall (depending on geo and audience). For example, for rewarded video in the US, the improvement can be substantial. With 100,000 rewarded video impressions per day, switching from waterfall to In-App Bidding can generate additional daily revenue.
ironSource (Unity Ads) has a strong position in the gaming segment, especially rewarded video. Mintegral covers the Asian audience well.
Setting up mediation requires ATT (App Tracking Transparency) on iOS 14+. Without requestTrackingAuthorization, ad CPM drops by 3-5 times for non-consenting users. SKAdNetwork and Privacy Manifest (iOS 17) are mandatory requirements; without them, review fails.
| Network |
Ad Type |
Feature |
| AdMob |
banner, interstitial, rewarded |
Wide network, easy start |
| MAX (AppLovin) |
rewarded, interstitial |
In-App Bidding, high fill rate |
| ironSource |
rewarded video |
Best for games |
| Mintegral |
rewarded, native |
Asia, programmatic |
How We Implement Monetization: Step-by-Step Process
- Current model audit — analysis of funnel, paywall, price tiers, and identification of bottlenecks.
- Model design — choose type (subscription, consumable, non-consumable) and optimize price points.
- IAP integration — set up StoreKit 2 / Google Billing 6, receipt validation, webhooks.
- Ad mediation — connect 3-6 networks, configure waterfall or In-App Bidding, test fill rate.
- Analytics and cohorts — integrate RevenueCat, Amplitude, or Firebase for LTV tracking.
- A/B testing of paywall — use Remote Config for experiments without a release.
- Launch and monitoring — 2 weeks of free support after launch, bug fixes by 24-hour SLA.
How to Design a Freemium Model and Paywall?
Freemium works when the boundary between free and paid is properly drawn. Too strict a paywall at the start — users delete. Too generous a free tier — no incentive to pay.
A technically sound pattern: server-side feature flags (Remote Config in Firebase or LaunchDarkly) control access to features. This allows A/B testing of paywall without a release, changing trial conditions, and running promotions.
Implementation at the code level: EntitlementManager — a single point for checking access to features, aware of subscription status, flags, and promos. No scattered isPremium checks throughout the code. Experience shows this approach reduces paywall-related bugs by 80% (confirmed on 30+ projects).
Checklist of Typical Monetization Mistakes
- Missing handling of
unfinished transactions — revenue loss of 5-10%.
- No server-side idempotency for consumable processing — double crediting.
- Forgot to call
acknowledgePurchase() on Android — purchase cancelled after 3 days.
- Not handling
REFUND and DID_RENEW events — incorrect subscription status.
- Paywall without A/B testing — leaving 20-40% of monetization potential.
- Ads from a single source (e.g., AdMob without mediation) — CPM 15-30% lower.
Scope of Monetization Work
- Current model audit — analysis of funnel, paywall, price tiers.
- IAP integration — StoreKit 2 / Google Billing 6, receipt validation, webhooks.
- Ad mediation — configure MAX / AdMob, connect 3-6 networks, test fill rate.
- Analytics setup — RevenueCat, Amplitude / Firebase, cohort analysis.
- Documentation — description of entitlements, restoration procedure, review checklist.
- Team training — analysis of typical mistakes, support recommendations.
- Guarantee — 2 weeks free support after launch, bug fixes by 24-hour SLA.
Estimated Timelines
| Stage |
Duration |
| Basic IAP (one store) |
1–2 weeks |
| Subscription system + RevenueCat + paywall |
3–5 weeks |
| Ad mediation (MAX + 3 networks) |
1–2 weeks |
| Full cycle (IAP + ads + analytics) |
4–8 weeks |
Cost is calculated individually. We have been working in this field for over 8 years and have implemented monetization in over 40 projects — many of which passed App Store Review without a single rejection. Contact us for an audit or order a consultation — we'll tell you what growth points exist in your app.