Thank you @PavanSomarathne for providing the purchase data! This revealed the actual root cause of the issue.
Both transactions show "isUpgradedIOS": false:
Monthly (arrives first):
{
"id": "2000001031579336",
"productId": "oxiwearmedicalpromonthly",
"transactionDate": 1760118720000,
"isUpgradedIOS": false, // ❌ Expected true, but is false!
"reasonIOS": "renewal"
}Yearly (arrives 4 minutes later):
{
"id": "2000001031579845",
"productId": "oxiwearmedicalproyearly",
"transactionDate": 1760118900000, // 180 seconds later
"isUpgradedIOS": false,
"reasonIOS": "renewal"
}The isUpgraded check didn't work because StoreKit reports subscription upgrades as "renewal" events, not as upgraded transactions. Both transactions have:
isUpgradedIOS: falsereasonIOS: "renewal"- Same
subscriptionGroupIdIOS: "21609681" - Same
originalTransactionIdentifierIOS
This happens because the upgrade occurred at the renewal boundary in Sandbox.
Instead of relying on isUpgraded, we now track the latest transaction date per subscription group:
-
Added subscription group tracking in
IapState.swift:- Tracks the latest
purchaseDatefor eachsubscriptionGroupID - Skips older transactions when a newer one exists in the same group
- Tracks the latest
-
Enhanced filtering in
startTransactionListener():// For subscriptions, skip if we've already seen a newer transaction in the same group if await self.state.shouldProcessSubscriptionTransaction(transaction) == false { OpenIapLog.debug("⏭️ Skipping older subscription transaction: \(transactionId) (superseded by newer transaction in same group)") continue }
-
Added comprehensive logging:
- Transaction details (ID, product, dates, subscription group)
- Skip reasons
- Emit confirmations
-
Monthly transaction arrives (transactionDate: 1760118720000)
- ✅ First in group "21609681" → Process and emit
-
Yearly transaction arrives (transactionDate: 1760118900000)
- ✅ Newer than monthly (180 seconds later) → Process and emit
- If monthly arrives again → ⏭️ Skip (older than yearly)
- ✅ Works regardless of
isUpgradedvalue - ✅ Handles both immediate and delayed upgrades
- ✅ Prevents old transactions from being emitted multiple times
- ✅ Maintains support for normal renewals
- ✅ Added detailed logging for debugging
All unit tests pass. The next release will include these changes.
With this fix:
- Monthly subscription will emit once
- Yearly subscription will emit once when it arrives
- Monthly won't re-emit after yearly is processed
- Detailed logs will show exactly what's happening
Would appreciate if you can test the next version and share the console logs!