Summary:
This article describes an issue where iOS consumable products cannot be purchased again after the purchase is confirmed, but only under specific conditions (e.g., TestFlight on certain iOS versions/devices). The root cause is uncompleted or re‑processed Apple StoreKit transactions, combined with stricter TestFlight behavior and an iOS bug where transactions may not finish. The resolution involves auto‑confirming consumable purchases in OnPurchasePending, ensuring correct confirmation logic, and testing on updated iOS builds/devices.
Symptoms:
- After successfully purchasing a consumable product and calling ConfirmPurchase, attempting to buy the same product again fails.
- In TestFlight on specific devices, OnPurchaseFailed is triggered with FailureReason = DuplicateTransaction and Apple’s dialog shows “Already purchased,” blocking repurchase.
- When running the same build from Xcode (sandbox), repurchase works and OnPurchasePending is called again.
- Purchase history and transaction IDs differ between TestFlight and Xcode / other devices.
Cause:
- Consumable transactions are not consistently or immediately confirmed in OnPurchasePending, leaving Apple StoreKit transactions in a pending/unfinished state.
- TestFlight enforces stricter, production-like behavior, so unfinished consumable transactions block subsequent purchases and are treated as duplicate transactions.
- The Unity IAP plugin re-detects previously recorded transaction IDs and classifies them as ConfirmedOrder, returning DuplicateTransaction instead of treating them as new purchases.
- On some iOS versions (prior to an iOS 26 fix), there is a known Apple-side issue where transactions might not finish, causing subsequent purchases of the same product to fail.
Resolution:
- Confirm consumable purchases immediately and automatically inside the OnPurchasePending callback instead of deferring confirmation to a later user action (e.g., a button).
- Ensure ConfirmPurchase is always called for consumables, regardless of additional receipt validation results, so transactions are removed from the pending state.
- Review the purchase flow to confirm that the same pending order (e.g., m_LastPendingOrder) is used for confirmation and that GetPurchases() is not misused for confirming unrelated orders.
- Test on devices running newer iOS versions (e.g., iOS 26 or later), where Apple has fixed an issue causing transactions not to finish and subsequent purchases to fail.
- When possible, test by building directly to a device instead of relying exclusively on TestFlight, as TestFlight can exhibit stricter or more pronounced behavior regarding unfinished transactions.