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]
See an example request →

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.

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.

InformationiOS ClientAndroid ClientServer (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 onlyIncludeActiveItemsIOS to true, so iOS reads Transaction.currentEntitlements; pass { onlyIncludeActiveItemsIOS: false } to read Transaction.all including expired and revoked entries. Android's queryPurchasesAsync returns 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.

1. initConnection()
2. getAvailablePurchases() → [PurchaseIOS]
3. For each purchase:
→ check purchaseState
→ validate with server
→ update local entitlements
→ finishTransaction() for unfinished transactions
Note: iOS transaction queue persists unfinished transactions

New Purchase Flow#

When a user initiates a new subscription purchase. Purchase states differ between platforms:

1. requestPurchase({ request: { apple: { sku } }, type: 'subs' }) → StoreKit payment sheet
2. purchaseUpdatedListener receives PurchaseIOS
→ validate → deliver → finishTransaction()
A delivered 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:

1. getActiveSubscriptions() → [ActiveSubscription]
2. For each subscription:
• isActive = true → grant access
• check renewalInfoIOS for details:
- willAutoRenew = false → show renewal prompt
- isInBillingRetry = true → show payment issue
- pendingUpgradeProductId → show pending change
• check expirationDateIOS on the subscription → show expiry info
3. No active subscriptions → revoke access

Detecting Cancellations#

Users can cancel subscriptions at any time. The subscription remains active until expiration.

Detection: willAutoRenew = false
User still has access:
• Until expirationDateIOS on ActiveSubscription
Actions:
→ Show "subscription ends on [expirationDateIOS]"
→ Offer re-subscribe option
→ Keep access until expiration

Handling Expiration#

When a subscription expires (cancelled + period ended), revoke access:

1. getActiveSubscriptions() returns empty or no matching product
2. Verify with server (recommended)
3. Revoke premium access
4. Show re-subscribe prompt

Restoring Purchases#

Users may need to restore subscriptions on new devices or after reinstalling:

1. User taps "Restore Purchases"
2. getAvailablePurchases() → [PurchaseIOS]
3. StoreKit fetches from Apple ID's purchase history
4. Validate each purchase with server
5. Grant access for valid subscriptions
6. finishTransaction() for each restored purchase
Note: iOS requires "Restore Purchases" button per App Store guidelines

Example Scenario#

Understanding how subscription states change over time helps implement correct handling:

Timeline: Cancelled Subscription
Day 1: User cancels subscription
• Subscription still valid until Day 30 (billing period end)
• iOS: willAutoRenew = false
• Android: autoRenewingAndroid === false when reported; null means unknown
Day 15: User restores purchases
• getAvailablePurchases() returns the purchase (not expired)
• ✅ Grant access (still valid until Day 30)
Day 35: User restores purchases
• iOS: currentEntitlements returns empty (expired)
• Android: queryPurchases returns empty (expired)
• ✅ No access (correctly expired)

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 productId and autoRenewPreference.
  • 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. renewalOfferType carries 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:

  1. Immediately after upgrade: The productId may still show the old tier (monthly), but autoRenewPreference shows the new tier (yearly). The pendingUpgradeProductId is set to the new tier.
  2. After processing (few minutes): The productId updates to the new tier (yearly), and pendingUpgradeProductId becomes null since 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: isActive is true but willAutoRenew is false. User has access until expiration.
  • Grace Period: gracePeriodExpirationDate has a value. Billing failed but user still has access temporarily.
  • Billing Retry: isInBillingRetry is 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.

Summary#

iOS

  • Rich client-side data via RenewalInfoIOS
  • Use pendingUpgradeProductId for 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, and pendingPurchaseUpdateAndroid
  • 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