fix(ios): emit error on duplicate purchase instead of silent hang#3177
fix(ios): emit error on duplicate purchase instead of silent hang#3177
Conversation
When iOS detects a duplicate purchase event, it previously logged a warning and returned silently, causing neither onPurchaseSuccess nor onPurchaseError to fire. This left the app in an infinite loading state with no way to recover. Now sends a 'duplicate-purchase' error via onPurchaseError so apps can detect this scenario and trigger restorePurchases or show recovery UI. Closes #3176 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughDuplicate iOS purchase updates now emit a structured Changes
Sequence Diagram(s)sequenceDiagram
participant App as "App (JS)"
participant Native as "HybridRnIap (iOS)"
participant Listener as "JS Listener / Handler"
App->>Native: requestPurchase / purchase flow
Native->>Native: receive purchase update
alt duplicate detected
Native->>Listener: emit NitroPurchaseResult(code: "duplicate-purchase") via sendPurchaseError
Listener->>Listener: normalize error (errorMapping)
Listener->>App: call onPurchaseError / recoverable message ("This purchase has already been processed. Try restoring purchases.")
else new/valid purchase
Native->>Listener: emit success purchase result
Listener->>App: call onPurchaseSuccess
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~15 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3177 +/- ##
==========================================
+ Coverage 69.64% 69.72% +0.08%
==========================================
Files 9 9
Lines 1825 1830 +5
Branches 596 597 +1
==========================================
+ Hits 1271 1276 +5
Misses 549 549
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request introduces a new DuplicatePurchase error code and implements logic to handle duplicate purchase events on iOS and in the TypeScript utility layers. The changes include emitting a specific error when duplicates are detected, adding the error to the ErrorCode enum, and updating error mapping and recovery logic. Review feedback highlights a redundant and inconsistent implementation of the isDuplicatePurchaseError utility across two files and suggests using a constant instead of a magic string in the iOS implementation.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/types.ts`:
- Line 330: The DuplicatePurchase enum member was added manually to src/types.ts
but this file is generated; remove the manual edit and instead add the new enum
value to the source schema/spec that drives generation (the enum that produces
DuplicatePurchase), then run the generation command (yarn generate:types) and
commit the regenerated outputs; ensure the change touches the authoritative
schema/spec (not src/types.ts) and verify CI passes with the regenerated file
containing DuplicatePurchase = 'duplicate-purchase'.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7ddf224-b241-448e-ab1a-84364d828aa4
📒 Files selected for processing (4)
ios/HybridRnIap.swiftsrc/types.tssrc/utils/error.tssrc/utils/errorMapping.ts
Move DuplicatePurchase out of auto-generated src/types.ts and define it as DUPLICATE_PURCHASE_CODE in errorMapping.ts. The CI step "Ensure generated types are up to date" regenerates types.ts from OpenIAP upstream, so local additions cause a diff failure. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove duplicate isDuplicatePurchaseError from error.ts; re-export the errorMapping.ts version which uses normalizing extractCode - Define static duplicatePurchaseCode constant in HybridRnIap.swift to eliminate the magic string Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…E_CODE Add tests across all three error test files: - isDuplicatePurchaseError with various input formats - DUPLICATE_PURCHASE_CODE constant value - getUserFriendlyErrorMessage for duplicate-purchase - isRecoverableError includes duplicate-purchase Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/utils/__tests__/errorMapping.test.ts (1)
155-166: Consider adding a string argument test for consistency.The
isUserCancelledErrortests include string-only argument cases (lines 149-150). IfisDuplicatePurchaseErrorsupports the same signature, consider adding a similar test:expect(isDuplicatePurchaseError('duplicate-purchase')).toBe(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/__tests__/errorMapping.test.ts` around lines 155 - 166, Add a string-argument test for isDuplicatePurchaseError to mirror isUserCancelledError's coverage: update the test block for isDuplicatePurchaseError to include an assertion like expect(isDuplicatePurchaseError('duplicate-purchase')).toBe(true), keeping the existing object-argument assertions (e.g., {code: DUPLICATE_PURCHASE_CODE}) intact so both string and object signatures are validated for the isDuplicatePurchaseError function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/utils/__tests__/errorMapping.test.ts`:
- Around line 155-166: Add a string-argument test for isDuplicatePurchaseError
to mirror isUserCancelledError's coverage: update the test block for
isDuplicatePurchaseError to include an assertion like
expect(isDuplicatePurchaseError('duplicate-purchase')).toBe(true), keeping the
existing object-argument assertions (e.g., {code: DUPLICATE_PURCHASE_CODE})
intact so both string and object signatures are validated for the
isDuplicatePurchaseError function.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5abc0f75-241a-402d-8864-68a64ec745b0
📒 Files selected for processing (3)
src/__tests__/utils/error.test.tssrc/__tests__/utils/errorMapping.test.tssrc/utils/__tests__/errorMapping.test.ts
✅ Files skipped from review due to trivial changes (1)
- src/tests/utils/error.test.ts
Summary
ErrorCode.DuplicatePurchase('duplicate-purchase') so apps can detect and recover from this scenarioisDuplicatePurchaseError()public helper for convenient error checkingCloses #3176
Changes
iOS Native (
ios/HybridRnIap.swift)sendPurchaseUpdate, now callssendPurchaseErrorwith codeduplicate-purchaseinstead of silently returningonPurchaseErrorcallback now fires, allowing apps to respond (e.g., triggerrestorePurchases)TypeScript Types (
src/types.ts)DuplicatePurchase = 'duplicate-purchase'toErrorCodeenumError Utilities (
src/utils/error.ts)isDuplicatePurchaseError()public helper (exported viareact-native-iap)Error Mapping (
src/utils/errorMapping.ts)DuplicatePurchasetoCOMMON_ERROR_CODE_MAP"This purchase has already been processed. Try restoring purchases."DuplicatePurchasetoisRecoverableErrorlist (recoverable viarestorePurchases)isDuplicatePurchaseError()helperUsage
Test plan
yarn typecheckpassesyarn lintpassesyarn testpasses (260 tests, 12 suites)🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
New Features
Tests