Skip to main content

Subscription Management Example

This guide demonstrates how to implement subscription management with React Native IAP, including subscription status checking, renewal handling, and subscription management UI.

Complete Example

For a production-ready subscription implementation, please refer to our complete example:

📱 SubscriptionFlow.tsx

This example demonstrates:

  • ✅ Subscription product loading and display
  • ✅ Active subscription detection
  • ✅ Platform-specific subscription handling
  • ✅ Auto-renewal status management
  • ✅ Grace period handling
  • ✅ Purchase restoration
  • ✅ Error handling with retry logic
  • ✅ Subscription status UI

Key Implementation Points

Platform-Specific Subscription Properties

When checking subscription status, different platforms provide different properties:

iOS Subscription Properties

  • expirationDateIOS: Unix timestamp (milliseconds) when subscription expires
  • originalTransactionDateIOS: Original purchase date
  • environmentIOS: 'Production' or 'Sandbox'

Android Subscription Properties

  • autoRenewingAndroid: Boolean indicating if subscription will auto-renew
  • purchaseStateAndroid: Purchase state (0 = purchased, 1 = canceled)

Key Differences

  • iOS: Check expirationDateIOS against current time to determine if active
  • Android: Check autoRenewingAndroid - if false, the user has canceled

⚠️ Important: Always validate subscription status on your server for production apps.

Basic Implementation Pattern

1. Load Subscriptions

import { useIAP } from 'react-native-iap'

const SUBSCRIPTION_IDS = ['com.app.monthly', 'com.app.yearly']

const {
connected,
subscriptions,
activeSubscriptions,
fetchProducts,
getActiveSubscriptions,
} = useIAP()

useEffect(() => {
if (connected) {
fetchProducts({ skus: SUBSCRIPTION_IDS, type: 'subs' })
getActiveSubscriptions()
}
}, [connected])

2. Check Active Subscription Status

const checkSubscriptionStatus = async () => {
const subs = await getActiveSubscriptions()

// Check if user has specific subscription
const hasActiveSubscription = subs.some(
(sub) => sub.productId === 'com.app.monthly' && sub.isActive
)

return hasActiveSubscription
}

3. Handle Subscription Purchase

const handleSubscription = async (productId: string) => {
try {
await requestPurchase({
request: {
ios: {
sku: productId,
appAccountToken: 'user-123',
},
android: {
skus: [productId],
subscriptionOffers:
subscription?.subscriptionOfferDetails?.map((offer) => ({
sku: productId,
offerToken: offer.offerToken,
})) || [],
},
},
type: 'subs',
})
} catch (error) {
console.error('Subscription failed:', error)
}
}

4. Process Successful Purchase

const { finishTransaction } = useIAP({
onPurchaseSuccess: async (purchase) => {
// Validate receipt on your server
const isValid = await validateReceiptOnServer(purchase)

if (isValid) {
// Grant subscription benefits
await grantSubscriptionAccess(purchase)

// Finish the transaction
await finishTransaction({
purchase,
isConsumable: false, // Subscriptions are non-consumable
})
}
},
})

Server-Side Validation

Example Validation Endpoint

app.post('/validate-subscription', async (req, res) => {
const { receipt, productId, purchaseToken } = req.body

try {
let validationResult

if (purchaseToken) {
// Android: Validate with Google Play
validationResult = await validateGooglePlaySubscription(
productId,
purchaseToken
)
} else {
// iOS: Validate with App Store
validationResult = await validateAppStoreReceipt(receipt)
}

res.json({
isActive: validationResult.isActive,
expirationDate: validationResult.expirationDate,
autoRenewing: validationResult.autoRenewing,
})
} catch (error) {
res.status(500).json({ error: 'Validation failed' })
}
})

Platform-Specific Features

iOS Subscription Features

  • Introductory offers
  • Promotional offers
  • Family sharing
  • Subscription status API (iOS 15.0+)

Android Subscription Features

  • Multiple subscription offers
  • Base plans and offers
  • Grace period handling
  • Upgrade/downgrade proration

Best Practices

  1. Always validate receipts server-side - Never trust client-side validation alone
  2. Handle grace periods - Continue providing access during payment retry periods
  3. Implement restore purchases - Essential for users switching devices
  4. Check subscription status on app launch - Ensure access is current
  5. Use activeSubscriptions helper - Simplifies status checking across platforms
  6. Test thoroughly - Use sandbox/test accounts on both platforms

Common Subscription Scenarios

Check if User Has Any Active Subscription

const hasAnyActiveSubscription = activeSubscriptions.length > 0

Check for Specific Subscription Tier

const hasPremium = activeSubscriptions.some(
(sub) => sub.productId === 'com.app.premium_yearly' && sub.isActive
)

Display Subscription Expiration

{
activeSubscriptions.map((sub) => (
<View key={sub.productId}>
<Text>Subscription: {sub.productId}</Text>
{sub.expirationDateIOS && (
<Text>
Expires: {new Date(sub.expirationDateIOS).toLocaleDateString()}
</Text>
)}
{Platform.OS === 'android' && (
<Text>Auto-renewing: {sub.autoRenewingAndroid ? 'Yes' : 'No'}</Text>
)}
</View>
))
}

Troubleshooting

Common Issues

  1. Subscription not showing as active

    • Check if finishTransaction was called
    • Verify server-side validation is working
    • Ensure subscription IDs match store configuration
  2. Can't purchase subscription

    • Verify subscription is approved in store console
    • Check if user already has active subscription
    • Ensure test accounts are properly configured
  3. Restoration not working

    • Call getAvailablePurchases() for restoration
    • Validate restored purchases server-side
    • Handle platform-specific restoration flows

See Also