feat: add getAllTransactionsIOS wrappers and docs updates#101
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 31 minutes and 19 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (19)
📝 WalkthroughWalkthroughThis PR adds a cross-platform Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Code Review
This pull request implements the getAllTransactionsIOS method across the Expo, Flutter, Godot, and React Native libraries to support retrieving the full StoreKit 2 transaction history on iOS. Additionally, it updates the documentation with a governance draft notice, expanded landscape details, and revised budget estimates. Feedback for the React Native iOS implementation suggests batching state updates on the MainActor to avoid unnecessary context switches and refining error handling to throw OpenIapException instead of returning an empty array on failure.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/docs/src/styles/documentation.css (1)
569-580:⚠️ Potential issue | 🟠 MajorDark mode still applies external-link styling to
.btnlinks.You excluded
.btnin light-mode target-blank rules, but the dark-mode rules at Line [652] and Line [658] still include.btn. This reintroduces dotted-border/link styling on buttons in dark theme.Proposed fix
-:root.dark .doc-page a.external-link, -:root.dark .doc-page a[target='_blank'] { +:root.dark .doc-page a.external-link, +:root.dark .doc-page a[target='_blank']:not(.btn) { @@ -:root.dark .doc-page a.external-link:hover, -:root.dark .doc-page a[target='_blank']:hover { +:root.dark .doc-page a.external-link:hover, +:root.dark .doc-page a[target='_blank']:not(.btn):hover {Also applies to: 594-594
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/styles/documentation.css` around lines 569 - 580, The dark-mode CSS blocks still apply external-link styling to buttons; update the dark-mode selectors (the ones matching .doc-page a.external-link:hover and .doc-page a[target='_blank']:not(.btn):hover equivalents in the dark theme) to also exclude .btn (e.g., use :not(.btn) where appropriate) so that buttons do not receive the dotted-border/link styling in dark mode; make the same change for the other affected selector mentioned (the one around the midpoint of the external-link rules).
🧹 Nitpick comments (6)
packages/docs/src/pages/docs/foundation/one-pager.tsx (1)
33-100: Consider splitting this into shorter doc blocks for scannability.Great content coverage, but this paragraph is very dense. Breaking it into 2–3 shorter paragraphs (or a short list of platforms/stores) would improve readability on docs pages.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/pages/docs/foundation/one-pager.tsx` around lines 33 - 100, The long dense JSX paragraph that lists platforms and stores (the block containing the multiple <a> tags for Meta Horizon OS, Vega OS, Amazon Fire OS, Galaxy Store, Amazon Appstore, Onside, Google Play, and Apple App Store) should be split into 2–3 shorter paragraphs or converted to a short bulleted list to improve scannability; update the JSX in the one-pager component so each logical group (new platforms, alternative marketplaces, and established stores) is its own <p> or an unordered list element, preserving the existing anchor tags, spacing and rel/target attributes while keeping the original wording.packages/docs/src/pages/docs/foundation/governance.tsx (1)
17-31: LGTM! Clean implementation of the draft notice.The draft notice is well-styled, clearly visible, and appropriately positioned. The inline styles are consistent with the existing pattern in this file (see lines 392-396), and the use of CSS custom properties ensures theme compatibility.
Optional: If this draft notice pattern will be reused across other documentation pages, consider extracting it to a shared
<DraftNotice>component to maintain consistency and reduce duplication.♻️ Example extraction (if reuse is anticipated)
Create
packages/docs/src/components/DraftNotice.tsx:interface DraftNoticeProps { children: React.ReactNode; } export function DraftNotice({ children }: DraftNoticeProps) { return ( <div style={{ background: 'var(--bg-secondary)', border: '1px solid var(--border-color)', borderLeft: '3px solid var(--primary-color)', borderRadius: '4px', padding: '0.75rem 1rem', marginBottom: '1.5rem', fontSize: '0.9rem', color: 'var(--text-secondary)', }} > {children} </div> ); }Then use it here:
<DraftNotice> <strong>Draft</strong> — The Foundation section is currently being prepared. Content may change as the governance structure is finalized. </DraftNotice>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/docs/src/pages/docs/foundation/governance.tsx` around lines 17 - 31, Approve the existing inline draft notice but optionally refactor it into a reusable DraftNotice component: create a DraftNotice React component (e.g., export function DraftNotice({ children }: DraftNoticeProps)) that returns the current styled div, move the hard-coded notice content into usage of <DraftNotice> in governance.tsx, and replace the inline style block with the new <DraftNotice> component to avoid duplication if this pattern will be reused across docs.libraries/expo-iap/src/modules/ios.ts (1)
360-365: Add JSDoc for the new exportedgetAllTransactionsIOSAPI.Please add a short JSDoc block (params/returns/platform) like neighboring exports to keep the public surface consistent.
As per coding guidelines, “Add JSDoc comments for public functions and exported APIs.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libraries/expo-iap/src/modules/ios.ts` around lines 360 - 365, Add a JSDoc block above the exported getAllTransactionsIOS function (the export const getAllTransactionsIOS: QueryField<'getAllTransactionsIOS'> = async () => { ... }) matching the style of neighboring exports: short description, `@returns` {Promise<PurchaseIOS[]>} describing the returned transactions array, and `@platform` ios; note there are no params so omit `@param`. Ensure the comment sits immediately above the export so the public API docs include it.libraries/react-native-iap/src/index.ts (1)
1083-1107: Add JSDoc for this new public query export.
getAllTransactionsIOSis now part of the public API surface, so it should carry a short JSDoc block like adjacent exported wrappers.As per coding guidelines `**/*.{ts,tsx,js,jsx}: Add JSDoc comments for public functions and exported APIs`.Suggested doc block
+/** + * Get full StoreKit 2 transaction history (iOS only). + * Returns an empty array on non-iOS platforms. + * `@returns` Promise<PurchaseIOS[]> - All iOS transactions available from native StoreKit history + * `@platform` iOS + */ export const getAllTransactionsIOS: QueryField< 'getAllTransactionsIOS' > = async () => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libraries/react-native-iap/src/index.ts` around lines 1083 - 1107, Add a JSDoc comment block above the exported getAllTransactionsIOS function describing its purpose, platform behavior, return type and thrown errors; reference the function name getAllTransactionsIOS and mention that it returns an array of PurchaseIOS for iOS and an empty array on non-iOS platforms, and document that it may throw a PurchaseError created by createPurchaseError (include param/docs for possible error fields like code, message, responseCode, debugMessage) following the style used by adjacent exported wrappers.libraries/react-native-iap/src/__tests__/index.test.ts (1)
41-41: Add direct tests forgetAllTransactionsIOSbehavior.This mock wiring is good, but there’s still no explicit test for the new wrapper’s iOS path, non‑iOS fallback (
[]), and error normalization behavior.As per coding guidelines `**/*.test.{ts,tsx,js,jsx}: Write unit tests for all utility functions and business logic`.Suggested test cases
+ describe('getAllTransactionsIOS', () => { + it('returns mapped purchases on iOS', async () => { + (Platform as any).OS = 'ios'; + mockIap.getAllTransactionsIOS.mockResolvedValueOnce([ + { + id: 't-all-1', + productId: 'sku.all.1', + transactionDate: Date.now(), + platform: 'ios', + store: 'apple', + quantity: 1, + purchaseState: 'purchased', + isAutoRenewing: false, + }, + ]); + const result = await IAP.getAllTransactionsIOS(); + expect(result).toHaveLength(1); + expect(result[0]?.productId).toBe('sku.all.1'); + }); + + it('returns [] on non-iOS', async () => { + (Platform as any).OS = 'android'; + await expect(IAP.getAllTransactionsIOS()).resolves.toEqual([]); + }); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libraries/react-native-iap/src/__tests__/index.test.ts` at line 41, Add explicit unit tests in libraries/react-native-iap/src/__tests__/index.test.ts to cover the new iOS wrapper: write three tests that (1) assert getAllTransactionsIOS is called and its results are returned on iOS, (2) assert the non‑iOS fallback returns [] when platform is not iOS, and (3) assert error normalization behavior by mocking getAllTransactionsIOS to throw and verifying the wrapper converts the error into the expected normalized error shape; reference the mock function getAllTransactionsIOS and the public wrapper (e.g., getAllTransactions or the exported function that calls getAllTransactionsIOS) when arranging/mocking platform conditions.libraries/react-native-iap/ios/HybridRnIap.swift (1)
720-744: Consider extracting shared transaction mapping/caching logic.This block is nearly identical to
getPendingTransactionsIOS(), which increases drift risk. A small private helper would keep behavior aligned.♻️ Suggested refactor
func getAllTransactionsIOS() throws -> Promise<[NitroPurchase]> { return Promise.async { do { RnIapLog.payload("getAllTransactionsIOS", nil) let all = try await OpenIapModule.shared.getAllTransactionsIOS() - var unionPurchases: [OpenIAP.Purchase] = [] - for purchase in all { - let union = OpenIAP.Purchase.purchaseIos(purchase) - unionPurchases.append(union) - let raw = OpenIapSerialization.purchase(union) - if let identifier = raw["id"] as? String { - await MainActor.run { - self.purchasePayloadById[identifier] = raw - } - } - } - let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(unionPurchases)) + let payloads = try await self.mapAndCacheIosPurchases(all) RnIapLog.result("getAllTransactionsIOS", payloads) return payloads.map { RnIapHelper.convertPurchaseDictionary($0) } } catch { RnIapLog.failure("getAllTransactionsIOS", error: error) return [] } } }private func mapAndCacheIosPurchases(_ purchases: [PurchaseIOS]) async throws -> [[String: Any]] { var unionPurchases: [OpenIAP.Purchase] = [] for purchase in purchases { let union = OpenIAP.Purchase.purchaseIos(purchase) unionPurchases.append(union) let raw = OpenIapSerialization.purchase(union) if let identifier = raw["id"] as? String { await MainActor.run { self.purchasePayloadById[identifier] = raw } } } return RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(unionPurchases)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libraries/react-native-iap/ios/HybridRnIap.swift` around lines 720 - 744, The transaction mapping/caching logic in getAllTransactionsIOS is duplicated with getPendingTransactionsIOS; extract it into a private async helper (e.g., mapAndCacheIosPurchases(_ purchases: [PurchaseIOS]) async -> [[String: Any]]) that converts each PurchaseIOS to OpenIAP.Purchase, serializes with OpenIapSerialization.purchase, caches raw payloads into self.purchasePayloadById on MainActor, then returns the sanitized array via RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(...)); replace the loop in both getAllTransactionsIOS and getPendingTransactionsIOS with a call to this helper and then map the returned payloads with RnIapHelper.convertPurchaseDictionary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/docs/src/pages/docs/foundation/roadmap-budget.tsx`:
- Line 299: The total baseline cost shown as the fixed string
"<strong>~$380/month</strong>" should be changed to a range to reflect the
variability from the CI cost line ("GitHub Actions CI/CD: $0–50"); update the
markup in roadmap-budget.tsx so the total reads something like a range (e.g.,
"~$330–$380/month" or similar) instead of a single value, modifying the element
that currently renders "<strong>~$380/month</strong>" to compute or hardcode the
matching range.
In `@packages/docs/src/styles/documentation.css`:
- Around line 556-563: Stylelint fails because chained :not() pseudo-classes are
used in the selectors like ".doc-page a:not(.anchor-link):not(.btn)" and its
hover variant; replace chained :not() with a single :not() using complex-list
notation, e.g. change ".doc-page a:not(.anchor-link):not(.btn)" to ".doc-page
a:not(.anchor-link, .btn)" and likewise update the hover selector and the other
occurrence (the selector at the later block that also uses
:not(.anchor-link):not(.btn)) so all rules use :not(.anchor-link, .btn) instead
of chained :not().
---
Outside diff comments:
In `@packages/docs/src/styles/documentation.css`:
- Around line 569-580: The dark-mode CSS blocks still apply external-link
styling to buttons; update the dark-mode selectors (the ones matching .doc-page
a.external-link:hover and .doc-page a[target='_blank']:not(.btn):hover
equivalents in the dark theme) to also exclude .btn (e.g., use :not(.btn) where
appropriate) so that buttons do not receive the dotted-border/link styling in
dark mode; make the same change for the other affected selector mentioned (the
one around the midpoint of the external-link rules).
---
Nitpick comments:
In `@libraries/expo-iap/src/modules/ios.ts`:
- Around line 360-365: Add a JSDoc block above the exported
getAllTransactionsIOS function (the export const getAllTransactionsIOS:
QueryField<'getAllTransactionsIOS'> = async () => { ... }) matching the style of
neighboring exports: short description, `@returns` {Promise<PurchaseIOS[]>}
describing the returned transactions array, and `@platform` ios; note there are no
params so omit `@param`. Ensure the comment sits immediately above the export so
the public API docs include it.
In `@libraries/react-native-iap/ios/HybridRnIap.swift`:
- Around line 720-744: The transaction mapping/caching logic in
getAllTransactionsIOS is duplicated with getPendingTransactionsIOS; extract it
into a private async helper (e.g., mapAndCacheIosPurchases(_ purchases:
[PurchaseIOS]) async -> [[String: Any]]) that converts each PurchaseIOS to
OpenIAP.Purchase, serializes with OpenIapSerialization.purchase, caches raw
payloads into self.purchasePayloadById on MainActor, then returns the sanitized
array via RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(...));
replace the loop in both getAllTransactionsIOS and getPendingTransactionsIOS
with a call to this helper and then map the returned payloads with
RnIapHelper.convertPurchaseDictionary.
In `@libraries/react-native-iap/src/__tests__/index.test.ts`:
- Line 41: Add explicit unit tests in
libraries/react-native-iap/src/__tests__/index.test.ts to cover the new iOS
wrapper: write three tests that (1) assert getAllTransactionsIOS is called and
its results are returned on iOS, (2) assert the non‑iOS fallback returns [] when
platform is not iOS, and (3) assert error normalization behavior by mocking
getAllTransactionsIOS to throw and verifying the wrapper converts the error into
the expected normalized error shape; reference the mock function
getAllTransactionsIOS and the public wrapper (e.g., getAllTransactions or the
exported function that calls getAllTransactionsIOS) when arranging/mocking
platform conditions.
In `@libraries/react-native-iap/src/index.ts`:
- Around line 1083-1107: Add a JSDoc comment block above the exported
getAllTransactionsIOS function describing its purpose, platform behavior, return
type and thrown errors; reference the function name getAllTransactionsIOS and
mention that it returns an array of PurchaseIOS for iOS and an empty array on
non-iOS platforms, and document that it may throw a PurchaseError created by
createPurchaseError (include param/docs for possible error fields like code,
message, responseCode, debugMessage) following the style used by adjacent
exported wrappers.
In `@packages/docs/src/pages/docs/foundation/governance.tsx`:
- Around line 17-31: Approve the existing inline draft notice but optionally
refactor it into a reusable DraftNotice component: create a DraftNotice React
component (e.g., export function DraftNotice({ children }: DraftNoticeProps))
that returns the current styled div, move the hard-coded notice content into
usage of <DraftNotice> in governance.tsx, and replace the inline style block
with the new <DraftNotice> component to avoid duplication if this pattern will
be reused across docs.
In `@packages/docs/src/pages/docs/foundation/one-pager.tsx`:
- Around line 33-100: The long dense JSX paragraph that lists platforms and
stores (the block containing the multiple <a> tags for Meta Horizon OS, Vega OS,
Amazon Fire OS, Galaxy Store, Amazon Appstore, Onside, Google Play, and Apple
App Store) should be split into 2–3 shorter paragraphs or converted to a short
bulleted list to improve scannability; update the JSX in the one-pager component
so each logical group (new platforms, alternative marketplaces, and established
stores) is its own <p> or an unordered list element, preserving the existing
anchor tags, spacing and rel/target attributes while keeping the original
wording.
🪄 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: 23bfe2e8-697a-4cda-ad80-63b2c4c2bfd4
📒 Files selected for processing (15)
libraries/expo-iap/ios/ExpoIapModule.swiftlibraries/expo-iap/src/modules/ios.tslibraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swiftlibraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dartlibraries/godot-iap/addons/godot-iap/godot_iap.gdlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktlibraries/react-native-iap/ios/HybridRnIap.swiftlibraries/react-native-iap/src/__tests__/index.test.tslibraries/react-native-iap/src/index.tslibraries/react-native-iap/src/specs/RnIap.nitro.tspackages/docs/src/pages/docs/foundation/governance.tsxpackages/docs/src/pages/docs/foundation/one-pager.tsxpackages/docs/src/pages/docs/foundation/roadmap-budget.tsxpackages/docs/src/pages/docs/updates/versions.tsxpackages/docs/src/styles/documentation.css
4e01eb3 to
13206e6
Compare
Framework Libraries: - Add getAllTransactionsIOS wrapper to react-native-iap, expo-iap, flutter_inapp_purchase, and godot-iap (GDScript + iOS GDExtension) Docs: - Fix button border clipping and dark mode text (.btn exclusion from doc-page link styles, using :not(.anchor-link, .btn) notation) - Fix SPM badge tag filter (apple-v* -> 2.*) - Add Claude Code and Codex to operational costs, domain -> ~$3 - Expand "The Problem" with Horizon OS, Vega OS, HarmonyOS, Fire OS, Galaxy Store, Huawei AppGallery, Onside (with official links) - Add Foundation draft notice to all foundation pages - Add framework library patch releases to release notes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13206e6 to
3ab5e0e
Compare
Summary
getAllTransactionsIOSwrapper to all framework libraries (react-native-iap, expo-iap, flutter_inapp_purchase, godot-iap).btnexclusion froma[target='_blank']styles)apple-v*->2.*)Follows up on #100
Test plan
tsc --noEmit— passedtsc --noEmit— passedflutter analyze— passedtsc --noEmit+ prettier — passed🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Style