This repository was archived by the owner on Oct 16, 2025. It is now read-only.
Commit 48365cc
authored
fix: filter old subscription transactions during upgrade (#22)
# Problem
Fixes hyochan/react-native-iap#3054
When upgrading from a monthly subscription to a yearly subscription
(within the same subscription group), `onPurchaseSuccess` was emitting
old purchase objects first, and the correct upgraded transaction arrived
only after 3-4 minutes in iOS Sandbox.
### Root Cause
Analysis of actual purchase data from the issue reporter revealed that
**`isUpgraded` is not reliably set to `true`** during subscription
upgrades in Sandbox. Both the old and new transactions show
`isUpgradedIOS: false` with `reasonIOS: "renewal"`.
This happens because StoreKit treats subscription upgrades that occur at
renewal boundaries as "renewal" events, not "upgrade" events.
**Example data from issue reporter:**
Monthly (arrives first):
```json
{
"transactionDate": 1760118720000,
"isUpgradedIOS": false, // ❌ Not set despite being superseded
"reasonIOS": "renewal"
}
```
Yearly (arrives 180 seconds later):
```json
{
"transactionDate": 1760118900000,
"isUpgradedIOS": false,
"reasonIOS": "renewal"
}
```
## Solution
Instead of relying on the unreliable `isUpgraded` flag, we now **track
the latest transaction date per subscription group**:
```swift
// For subscriptions, skip if we've already seen a newer transaction in the same group
// This handles subscription upgrades where isUpgraded is not reliably set
if await self.state.shouldProcessSubscriptionTransaction(transaction) == false {
OpenIapLog.debug("⏭️ Skipping older subscription transaction: \(transactionId) (superseded by newer transaction in same group)")
continue
}
```
### How It Works
1. Track the latest `purchaseDate` for each `subscriptionGroupID`
2. When a transaction arrives, check if a newer transaction in the same
group has already been processed
3. Skip the older transaction if a newer one exists
4. This works regardless of the `isUpgraded` flag value
## Changes
**IapState.swift:**
- Added `latestTransactionDateByGroup` dictionary to track latest
transaction per subscription group
- Added `shouldProcessSubscriptionTransaction()` to determine if a
transaction should be processed
**OpenIapModule.swift:**
- Removed unreliable `isUpgraded` check
- Added subscription group-based filtering
- Enhanced debug logging to show transaction details
## Testing
- ✅ All unit tests passing (10 tests)
- ✅ Swift build successful
- ✅ Maintains existing functionality (subscription renewals, duplicate
prevention, refund filtering)
## Behavior After Fix
**Subscription Upgrade (Monthly → Yearly):**
- ❌ Before: Monthly emitted first, Yearly after 3-4 min delay
- ✅ After: Only the newest transaction (Yearly) is emitted; Monthly is
skipped as superseded
**Subscription Renewal:**
- ✅ Works as before (newer renewal transaction replaces older)
**Refunded Purchase:**
- ✅ Still filtered out (`revocationDate != nil`)
**Non-subscription IAP:**
- ✅ No change (group tracking only applies to subscriptions)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- New Features
- Per-subscription-group tracking to ensure only the most recent
transaction in a group is processed.
- Bug Fixes
- Skip revoked transactions to avoid invalid purchase events.
- Prevent duplicate or outdated subscription purchase updates by
filtering superseded transactions.
- Refactor
- Streamlined transaction processing and enhanced debug logging when
purchase updates are emitted.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->1 parent 391205a commit 48365cc
File tree
3 files changed
+149
-3
lines changed- Sources
- Helpers
3 files changed
+149
-3
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
11 | 14 | | |
12 | 15 | | |
13 | 16 | | |
| |||
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
| 24 | + | |
21 | 25 | | |
22 | 26 | | |
23 | 27 | | |
| |||
32 | 36 | | |
33 | 37 | | |
34 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
35 | 67 | | |
36 | 68 | | |
37 | 69 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
838 | 838 | | |
839 | 839 | | |
840 | 840 | | |
841 | | - | |
842 | | - | |
843 | | - | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
844 | 861 | | |
845 | 862 | | |
846 | 863 | | |
| |||
896 | 913 | | |
897 | 914 | | |
898 | 915 | | |
| 916 | + | |
899 | 917 | | |
900 | 918 | | |
901 | 919 | | |
| |||
0 commit comments