Use this page with AI
Copy this into your coding assistant and add your request.
Read https://openiap.dev/docs/lifecycle/subscription and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.
My request: [describe what customers should be able to do]Subscription
Understanding how subscriptions work on each platform is crucial for proper implementation. iOS and Android handle subscription data very differently, especially when it comes to renewal information.
Live example (full subscription flow — fetch, purchase, upgrade / downgrade, cancellation, restore): expo-iap · react-native-iap · flutter_inapp_purchase · kmp-iap
Android examples on this page describe Google Play. Community providers expose only the lifecycle data their store supplies and need that store's server verification. Installing a provider does not add its backend support to IAPKit.
Platform Comparison#
The key difference is where subscription information is available. iOS provides rich data client-side, while Android requires server-side calls for detailed information.
| Information | iOS Client | Android Client | Server (Both) |
|---|---|---|---|
| Auto-renew status | ✅ willAutoRenew | ⚠️ autoRenewingAndroid (null when unavailable) | ✅ |
| Next renewal product | ✅ autoRenewPreference | ❌ | ✅ |
| Pending upgrade/downgrade | ✅ pendingUpgradeProductId | ✅ pendingPurchaseUpdateAndroid | ✅ |
| Expiration reason | ✅ expirationReason | ❌ | ✅ |
| Grace period status | ✅ gracePeriodExpirationDate | ❌ | ✅ |
| Billing retry status | ✅ isInBillingRetry | ⚠️ isSuspendedAndroid (state only, no retry details) | ✅ |
| Renewal date | ✅ renewalDate | ❌ | ✅ |
| Detailed subscription state | ✅ | ❌ | ✅ |
Purchase Verification#
Regardless of platform, you should verify purchases using OpenIAP's APIs. These APIs retrieve the latest subscription data from the store and provide a unified interface.
Client-Side Verification#
Use these APIs to check subscription status in your app:
- getActiveSubscriptions: Returns only currently active subscriptions. Best for checking entitlements.
- getAvailablePurchases: Returns the purchases the store still holds — owned non-consumables, active subscriptions, and unfinished transactions. It is not a purchase-history source on either platform. The framework SDKs default
onlyIncludeActiveItemsIOStotrue, so iOS readsTransaction.currentEntitlements; pass{ onlyIncludeActiveItemsIOS: false }to readTransaction.allincluding expired and revoked entries. Android'squeryPurchasesAsyncreturns only currently-owned purchases and has no equivalent option. For full iOS history use getAllTransactionsIOS.
These APIs query the store directly and return the latest data, including renewal information on iOS.
Server-Side Verification (Recommended)#
For production apps, implement server-side validation for authoritative subscription status:
- iOS: App Store Server API + App Store Server Notifications V2
- Android: Google Play Developer API + RTDN (Real-time Developer Notifications)
Setting up server-side verification can be complex. OpenIAP's hosted backend IAPKit provides a simple, unified API for server-side receipt validation across both iOS and Android platforms.
With IAPKit, you can verify purchases, manage subscriptions, and handle webhooks without building complex server infrastructure from scratch.
Learn more about IAPKit integration in our announcement.
Subscription Lifecycle#
This section shows how to handle subscription states throughout the app lifecycle. The flows apply to both iOS and Android unless noted.
On App Launch#
Check for existing subscriptions when the app starts. This handles purchases made while the app was closed.
New Purchase Flow#
When a user initiates a new subscription purchase. Purchase states differ between platforms:
PurchaseIOS always carries purchaseState: 'purchased' — StoreKit only hands the listener completed transactions, so the pending and unknown members of the shared enum never appear on iOS. Anything that is not a completed purchase arrives through purchaseErrorListener instead: Ask to Buy and other deferred payments as ErrorCode.DeferredPayment ('deferred-payment'), cancellations as ErrorCode.UserCancelled.Checking Subscription Status#
Periodically verify subscription status, especially for subscription state changes:
Detecting Cancellations#
Users can cancel subscriptions at any time. The subscription remains active until expiration.
Handling Expiration#
When a subscription expires (cancelled + period ended), revoke access:
Restoring Purchases#
Users may need to restore subscriptions on new devices or after reinstalling:
Example Scenario#
Understanding how subscription states change over time helps implement correct handling:
When to Validate#
Server validation is needed at these key points:
- After purchase — Verify the purchase is legitimate
- On restore — Check current status (active/cancelled/refunded/expired)
- Periodically for active subscriptions — Detect refunds and cancellations
- On app launch — Sync subscription state with server
iOS Subscription Overview#
iOS provides rich subscription data client-side through StoreKit 2. The RenewalInfoIOS type contains detailed renewal information that lets you build subscription management UI without server calls. However, server validation is still recommended for production apps.
RenewalInfoIOS Fields#
This type is available on PurchaseIOS and ActiveSubscription via the renewalInfoIOS property:
- willAutoRenew: Whether the subscription will automatically renew. If
false, the user has cancelled but still has access until expiry. - autoRenewPreference: The product ID that will be used at the next renewal. If different from the current product, the user has scheduled a tier change.
- pendingUpgradeProductId: Convenience field showing the pending tier change target. Calculated by comparing
productIdandautoRenewPreference. - renewalDate: Next renewal date (timestamp in milliseconds).
- expirationReason: StoreKit's raw integer expiration-reason value represented as a string (
"1","2", …), not a symbolic name. Preserve unknown future values rather than mapping them to a fallback. - gracePeriodExpirationDate: Grace period end date if in grace period due to billing issues.
- isInBillingRetry: Whether Apple is currently retrying a failed payment.
- renewalOfferId / renewalOfferType: The offer applied to the next renewal.
renewalOfferTypecarries values such as"PROMOTIONAL","SUBSCRIPTION_OFFER_CODE", and"WIN_BACK".
Detecting Tier Changes (Upgrade/Downgrade)#
Understanding how tier changes work on iOS is crucial for proper subscription management. The behavior differs between upgrades and downgrades.
Upgrade Flow#
When a user upgrades (e.g., monthly → yearly), Apple processes the change immediately with a prorated refund for the remaining time on the old plan. However, the purchase data updates in stages:
- Immediately after upgrade: The
productIdmay still show the old tier (monthly), butautoRenewPreferenceshows the new tier (yearly). ThependingUpgradeProductIdis set to the new tier. - After processing (few minutes): The
productIdupdates to the new tier (yearly), andpendingUpgradeProductIdbecomesnullsince there's no longer a pending change.
Downgrade Flow#
Downgrades (e.g., yearly → monthly) are scheduled to take effect at the end of the current billing period:
- productId: Shows current tier (yearly) - user keeps premium access
- autoRenewPreference: Shows future tier (monthly)
- pendingUpgradeProductId: Shows monthly (pending downgrade)
The user retains their current tier until expiry, then switches to the lower tier.
Other Subscription States#
- Cancellation:
isActiveis true butwillAutoRenewis false. User has access until expiration. - Grace Period:
gracePeriodExpirationDatehas a value. Billing failed but user still has access temporarily. - Billing Retry:
isInBillingRetryis true. Apple is retrying the payment.
Server-Side Validation#
While iOS provides rich client-side data, server validation is still recommended:
- App Store Server API: Verify subscription status and get transaction history
- App Store Server Notifications V2: Receive real-time webhook events (renewals, cancellations, refunds, Family Sharing changes)
Server validation is especially important for cross-platform apps, fraud prevention, and accurate analytics.
Related APIs#
- getActiveSubscriptions - Get active subscriptions with renewal info
- getAvailablePurchases - Get all purchases including expired
- subscriptionStatusIOS - Get detailed subscription status
- RenewalInfoIOS - Type reference
Summary#
iOS
- Rich client-side data via
RenewalInfoIOS - Use
pendingUpgradeProductIdfor tier change detection - Server-side recommended for production apps
- App Store Server Notifications V2 for webhooks
Android
- Client-side subscription lifecycle data limited to
autoRenewingAndroid,isSuspendedAndroid, andpendingPurchaseUpdateAndroid - Null renewal status means unknown, not cancelled
- Server-side required for detailed subscription info
- Use Google Play Developer API for authoritative data
- RTDN for real-time subscription updates