getAvailablePurchases does not returns unfinished transactions
Hi,
Scenario For Android
- Initiate request purchase
- Click on buy button
- Payment successful
- Immediately user interrupted by another app or app killed
- App restarts, getAvailablePurchases returns Empty list
- When Next time, Try to initiate the request purchase returns "You already own this item"
How can I fix or handle this case?
Below is my code fetch unfinished transactions
for (const purchase of purchases) { if (Platform.OS === 'android') { const androidPurchase = purchase as PurchaseAndroid; const body = { paymentProvider: IN_APP_PURCHASE_PROVIDER.GOOGLE_PLAY, purchaseToken: androidPurchase.purchaseToken, productId: androidPurchase.productId, receipt: '', transactionId: '', exTransactionId: androidPurchase.obfuscatedAccountIdAndroid, }; retryExclusiveContent(body); } else if (Platform.OS === 'ios') { const iosPurchase = purchase as PurchaseIOS; const body = { paymentProvider: IN_APP_PURCHASE_PROVIDER.APPLE, purchaseToken: iosPurchase.purchaseToken, productId: iosPurchase.productId, receipt: '', transactionId: iosPurchase.transactionId, exTransactionId: iosPurchase.appAccountToken, }; retryExclusiveContent(body); } finishTransactionPurchase(purchase); }
On Android, getAvailablePurchases() internally wraps:
BillingClient.queryPurchasesAsync(INAPP)
BillingClient.queryPurchasesAsync(SUBS)
So if the returned array is empty, it simply means:
Google Play returned no purchases from its query API.
Android Billing sometimes returns empty results due to:
The app being killed immediately after purchase (before onPurchasesUpdated).
Temporary caching or sync delays inside Google Play Billing.
Multiple Google accounts logged into the device.
A purchase being in a transitional state not yet reflected in the Billing client cache.
These situations are well-known and have been referenced across Google Billing discussions, even though many issue tracker links are internal and not publicly accessible.
- Why "You already own this item" appears
If Google considers the item purchased but the app has not acknowledged/consumed it, calling requestPurchase again triggers:
ITEM_ALREADY_OWNED "You already own this item"
This is expected behavior according to the Android Billing documentation:
A previously completed purchase must be acknowledged/consumed before another purchase can be initiated.
Official docs: https://developer.android.com/google/play/billing/errors#item-already-owned
- Recommended recovery strategy
A. Catch "already owned" and trigger restore
try { await requestPurchase({ sku: productId }); } catch (e) { if (isAlreadyOwnedError(e)) { // Allow Google Play some time to sync await delay(1000);
const purchases = await getAvailablePurchases();
await handleRestore(purchases); // validate + grant entitlement + finishTransaction
return;
}
throw e; }
B. Retry getAvailablePurchases()
Because Google sometimes returns an empty list temporarily, 1–2 retries with a short delay (500–1000 ms) can resolve it.
C. Provide a “Restore Purchases” button
This helps users recover entitlements when Google’s cache is delayed.
- What this means for react-native-iap
getAvailablePurchases() is the official, correct recovery method on Android.
If Google Play returns an empty list, the library cannot override or bypass that—it must rely on BillingClient’s state.
Apps should implement:
Error handling for ITEM_ALREADY_OWNED
Retry logic for restore
Manual restore option
finishTransaction only after successful server validation
- One-sentence conclusion
On Android, interrupted purchases may not immediately appear in getAvailablePurchases() due to Google Play Billing’s cache/update delays. The correct approach is to catch "You already own this item", retry restore, validate entitlements, and then finish the transaction.
Will check this and let you know.
@hyochan Thanks, working for Android.
Scenario For iOS
- Initiate request purchase
- Click on buy button
- Payment successful
- Immediately user interrupted by another app or app killed
- App restarts, getAvailablePurchases returns Empty list
- When Next time, Try to initiate the request purchase show no dialog. like android "Already own item popup"
How to handle same case for iOS?
@dharmeshgigs
On iOS, interrupted/unfinished transactions are stored separately. Use getPendingTransactionsIOS to retrieve them:
import {
getPendingTransactionsIOS,
finishTransaction,
getAvailablePurchases
} from 'react-native-iap';
const checkAndRecoverPurchases = async () => {
if (Platform.OS === 'ios') {
// 1. Check for pending (unfinished) transactions first
const pendingTransactions = await getPendingTransactionsIOS();
if (pendingTransactions.length > 0) {
console.log('Found pending transactions:', pendingTransactions);
for (const purchase of pendingTransactions) {
// 2. Verify with your server
const isValid = await verifyPurchaseOnServer(purchase);
if (isValid) {
// 3. Grant entitlement to user
await grantEntitlement(purchase.productId);
// 4. Finish the transaction
await finishTransaction({
purchase,
isConsumable: false, // true for consumables
});
}
}
}
}
};