While testing IAP before release, we hit a classic but very confusing issue:
- User taps “Continue Purchase”
- Button switches to “Processing”
- A few seconds later it returns to “Continue Purchase”
- No App Store purchase confirmation dialog appears
This article documents the full investigation path, key logs, root cause, and a practical fix strategy you can apply before launch.
1. Symptom
The product list loaded correctly on the purchase screen, but after tapping purchase:
purchase()was called- No system dialog appeared
- UI eventually returned to the not-purchased state
At first glance, it looked like the purchase flow never started, but logs showed a different story.
2. First pass: verify whether the purchase flow actually runs
We added logs to all critical checkpoints in the purchase flow:
- Button click parameters
purchaseSelectedProduct()entry andguardbranches- Before/after
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Result:
purchase()returned.success- Transaction verification succeeded, and
transaction.finish()was executed - But
entitledIDsremained empty
This means: it was not “purchase didn’t start,” but “entitlements weren’t recognized after purchase.”
3. Key findings after deeper investigation
After extending logs, several decisive signals appeared:
Transaction.currentEntitlementsiteration count was0Transaction.latest(for: monthly)had a valid record, but it was expired- More importantly, old transactions existed in
Transaction.unfinishedbefore purchase - The current
purchase()returned a transaction from an old chain, not from a new interactive purchase flow
That explains why there was no popup:
- StoreKit first returned a processable old transaction (unfinished / historical chain)
- Your code finished it via
finish() - The request looked “successful,” but it never went through the new purchase confirmation path users expect
4. Root cause summary
This wasn’t a single-point failure, but a combination of two factors:
- Unfinished transactions interfering with the purchase flow
- Sandbox subscription chain being in an expired state (even with a latest transaction, it may not be an active entitlement)
Final behavior: no popup, apparent success, entitlements still empty.
5. Fix strategy (validated in practice)
5.1 Proactively clear unfinished transactions before purchase
At app startup and before purchase, scan Transaction.unfinished and call finish() on verified transactions to prevent old transactions from hijacking the new purchase flow.
5.2 Keep and strengthen diagnostic logs (recommended before launch)
Keep these key logs enabled until release:
unfinished transactionsnapshotsubscription status(state, renewal info)currentEntitlementsiteration countlatest(for:)transaction/expiration details
5.3 Use a dual-path entitlement check (optional hardening)
If currentEntitlements is temporarily empty in some environments, use latest(for:) as a fallback (while still validating active status).
6. Pre-launch IAP checklist
- Before each test, verify the sandbox account’s subscription state (expired or not)
- Log
unfinishedcount on critical paths - Test three scenarios: first purchase / renew after expiration / restore purchases
- Check consistency between
currentEntitlementsandlatest - If “no popup” happens, first check whether an old transaction path was hit
7. Conclusion
“Tap purchase with no popup” is not always a UI issue or a timing issue.
In StoreKit 2, transaction state, unfinished queue, and subscription lifecycle all affect final behavior.
Adding these logs and cleanup steps before launch can eliminate many IAP issues that are hard to reproduce in production.