getAvailablePurchases()
Retrieves all non-consumed purchases made by the user.
Overview
The getAvailablePurchases()
method returns a list of purchases that haven't been consumed or finished. This is useful for restoring purchases, checking subscription status, and handling incomplete transactions.
Signatures
expo-iap Compatible
Future<List<Purchase>> getAvailablePurchases()
Legacy Method
Future<List<PurchasedItem>?> getAvailableItemsIOS()
Returns
A list of active purchases including:
- Non-consumable items
- Active subscriptions
- Unfinished transactions
- Purchases awaiting acknowledgment (Android)
Platform Behavior
iOS
- Returns all transactions in the payment queue
- Includes restored purchases
- Shows both finished and unfinished transactions
Android
- Returns purchases from Google Play
- Includes both in-app products and subscriptions
- Only shows purchases that haven't been consumed
Usage Examples
Basic Usage
// Get all available purchases
final purchases = await FlutterInappPurchase.instance.getAvailablePurchases();
for (var purchase in purchases) {
print('Active purchase: ${purchase.productId}');
print('Transaction ID: ${purchase.transactionId}');
}
Restore Purchases
class PurchaseRestorer {
final _iap = FlutterInappPurchase.instance;
Future<void> restorePurchases() async {
try {
// Show loading indicator
showLoading(true);
// Get all available purchases
final purchases = await _iap.getAvailablePurchases();
if (purchases.isEmpty) {
showMessage('No purchases to restore');
return;
}
// Process each purchase
for (var purchase in purchases) {
await _processPurchase(purchase);
}
showMessage('Restored ${purchases.length} purchases');
} catch (e) {
showError('Failed to restore purchases: $e');
} finally {
showLoading(false);
}
}
Future<void> _processPurchase(Purchase purchase) async {
// Verify the purchase
if (await _verifyPurchase(purchase)) {
// Unlock content
await _unlockContent(purchase.productId);
// Finish transaction if needed
if (_shouldFinishTransaction(purchase)) {
await _iap.finishTransaction(purchase);
}
}
}
}
Check Subscription Status
class SubscriptionChecker {
final _subscriptionIds = [
'com.example.monthly',
'com.example.yearly',
'com.example.premium',
];
Future<SubscriptionStatus> checkSubscriptionStatus() async {
try {
final purchases = await FlutterInappPurchase.instance.getAvailablePurchases();
// Find active subscriptions
final activeSubscriptions = purchases.where((p) =>
_subscriptionIds.contains(p.productId)
).toList();
if (activeSubscriptions.isEmpty) {
return SubscriptionStatus(
isActive: false,
activeProduct: null,
);
}
// Get the most recent subscription
activeSubscriptions.sort((a, b) =>
(b.transactionDate ?? DateTime(1970))
.compareTo(a.transactionDate ?? DateTime(1970))
);
final latestSubscription = activeSubscriptions.first;
return SubscriptionStatus(
isActive: true,
activeProduct: latestSubscription.productId,
expirationDate: _calculateExpirationDate(latestSubscription),
);
} catch (e) {
print('Error checking subscription: $e');
return SubscriptionStatus(isActive: false, activeProduct: null);
}
}
DateTime? _calculateExpirationDate(Purchase purchase) {
// Implementation depends on your subscription periods
// This is a simplified example
if (purchase.productId == 'com.example.monthly') {
return purchase.transactionDate?.add(Duration(days: 30));
} else if (purchase.productId == 'com.example.yearly') {
return purchase.transactionDate?.add(Duration(days: 365));
}
return null;
}
}
Handle Pending Transactions
class PendingTransactionHandler {
final _iap = FlutterInappPurchase.instance;
Future<void> processPendingTransactions() async {
final purchases = await _iap.getAvailablePurchases();
for (var purchase in purchases) {
if (_isPending(purchase)) {
print('Found pending transaction: ${purchase.productId}');
// Try to complete the transaction
await _completePendingTransaction(purchase);
}
}
}
bool _isPending(Purchase purchase) {
// Check platform-specific pending states
if (Platform.isAndroid) {
return purchase.purchaseStateAndroid == 'pending';
} else if (Platform.isIOS) {
// iOS transactions in queue are pending
return true;
}
return false;
}
Future<void> _completePendingTransaction(Purchase purchase) async {
try {
// Verify with backend
final isValid = await _verifyWithBackend(purchase);
if (isValid) {
// Deliver content
await _deliverContent(purchase.productId);
// Finish transaction
await _iap.finishTransaction(purchase);
}
} catch (e) {
print('Error completing pending transaction: $e');
// Transaction remains pending for retry
}
}
}
Filter Purchase Types
class PurchaseFilter {
List<Purchase> filterConsumables(List<Purchase> purchases) {
const consumableIds = ['coins_100', 'coins_500', 'gems_pack'];
return purchases.where((p) => consumableIds.contains(p.productId)).toList();
}
List<Purchase> filterNonConsumables(List<Purchase> purchases) {
const nonConsumableIds = ['remove_ads', 'unlock_pro', 'theme_pack'];
return purchases.where((p) => nonConsumableIds.contains(p.productId)).toList();
}
List<Purchase> filterSubscriptions(List<Purchase> purchases) {
const subscriptionIds = ['monthly_sub', 'yearly_sub'];
return purchases.where((p) => subscriptionIds.contains(p.productId)).toList();
}
List<Purchase> filterUnacknowledged(List<Purchase> purchases) {
if (Platform.isAndroid) {
return purchases.where((p) => p.isAcknowledgedAndroid == false).toList();
}
return [];
}
}
Complete Implementation
class PurchaseManager {
final _iap = FlutterInappPurchase.instance;
final _ownedProducts = <String>{};
Future<void> syncPurchases() async {
try {
// Get all available purchases
final purchases = await _iap.getAvailablePurchases();
// Clear current state
_ownedProducts.clear();
// Process each purchase
for (var purchase in purchases) {
// Add to owned products
_ownedProducts.add(purchase.productId);
// Handle based on platform
if (Platform.isAndroid && !purchase.isAcknowledgedAndroid!) {
// Acknowledge Android purchase
await _acknowledgeAndroidPurchase(purchase);
}
// Unlock content if not already unlocked
if (!await _isContentUnlocked(purchase.productId)) {
await _unlockContent(purchase.productId);
}
}
print('Synced ${_ownedProducts.length} purchases');
} catch (e) {
print('Error syncing purchases: $e');
}
}
bool isProductOwned(String productId) {
return _ownedProducts.contains(productId);
}
Future<void> _acknowledgeAndroidPurchase(Purchase purchase) async {
if (purchase.purchaseToken != null) {
await _iap.acknowledgePurchaseAndroid(
purchaseToken: purchase.purchaseToken!,
);
}
}
}
Best Practices
- Cache Results: Don't call this method too frequently
- Verify Purchases: Always verify purchases with your backend
- Handle Duplicates: Check for duplicate transactions
- Process All: Process all available purchases on app launch
- Error Recovery: Implement retry logic for failed processing
Common Use Cases
- Restore Purchases: After app reinstall or device change
- Subscription Check: Verify active subscription status
- Pending Transactions: Complete interrupted purchases
- Content Sync: Ensure all purchased content is unlocked
- Receipt Validation: Get receipts for server validation
Related Methods
getPurchaseHistory()
- Gets historical purchases (Android only)finishTransaction()
- Completes transactionsrestorePurchases()
- iOS-specific restore method
Platform Notes
iOS
- Includes all non-finished transactions
- May include very old transactions if not finished
- Restored purchases appear here
Android
- Only includes purchases from current Google account
- Consumed items don't appear
- Requires BILLING permission