Skip to content

fix: wire every type-declared handler across all wrapper SDKs#105

Merged
hyochan merged 11 commits into
mainfrom
fix/sdk-parity-all-libraries
Apr 24, 2026
Merged

fix: wire every type-declared handler across all wrapper SDKs#105
hyochan merged 11 commits into
mainfrom
fix/sdk-parity-all-libraries

Conversation

@hyochan
Copy link
Copy Markdown
Member

@hyochan hyochan commented Apr 24, 2026

Summary

Closes #104 — Flutter beginRefundRequestIOS was declared in types.dart but never implemented. A systematic 3-pass audit across every wrapper library uncovered the same bug pattern elsewhere; this PR wires all of them end-to-end.

After this PR, every handler declared in each library's generated types has a complete runtime path (public API + native bridge + bundle wiring).

Changes by library

packages/apple

  • Add @objc bridge for requestPurchaseOnPromotedProductIOS and deepLinkToSubscriptions so KMP's cinterop can reach them.

flutter_inapp_purchase

  • Add 7 iOS handlers (Swift method-channel cases + Dart getters + QueryHandlers/MutationHandlers bundle wiring): beginRefundRequestIOS, currentEntitlementIOS, latestTransactionIOS, isTransactionVerifiedIOS, getTransactionJwsIOS, getReceiptDataIOS, canPresentExternalPurchaseNoticeIOS.
  • Fix channel-name drift: syncIOS (end+init workaround → real AppStore.sync), subscriptionStatusIOS/getSubscriptionStatus, getAppTransactionIOS/getAppTransaction — Swift now accepts both names and the old bridge case was missing entirely.
  • Wire verifyPurchase, isBillingProgramAvailableAndroid, createBillingProgramReportingDetailsAndroid, and launchExternalLinkAndroid (adapter getter for named-param signature) into the MutationHandlers bundle.

kmp-iap

  • Replace 5 UnsupportedOperationException stubs in iosMain/InAppPurchaseIOS.kt with real ObjC bridge calls: beginRefundRequestIOS, syncIOS, getAllTransactionsIOS (stale TODO removed), plus requestPurchaseOnPromotedProductIOS and deepLinkToSubscriptions using the new bridge methods.

expo-iap

  • Export consumePurchaseAndroid (previously asymmetric vs acknowledgePurchaseAndroid).
  • Export getStorefrontIOS deprecated alias (declared in schema).

react-native-iap

  • Export validateReceiptIOS deprecated alias (declared in schema).

godot-iap

  • Add 5 missing APIs across the GDExtension Swift bridge and GDScript public API: validate_receipt, validate_receipt_ios, is_eligible_for_external_purchase_custom_link_ios, get_external_purchase_custom_link_token_ios, show_external_purchase_custom_link_notice_ios.

knowledge/internal/04-platform-packages.md

  • Add SDK Parity Checklist section: 5-layer per-library table (type declared / public API / platform bridge / handlers bundle / test coverage), platform-suffix rules, four observed failure patterns (phantom interface, UnsupportedOperationException stub, channel-name drift, handler bundle omission), and a grep-based audit command so future schema additions can be validated mechanically.
  • CLAUDE.md links to the new checklist from the required pre-work.

Test plan

  • packages/apple: swift build clean, swift test87/87 pass
  • flutter_inapp_purchase: flutter analyze clean, flutter test257/257 pass, xcodebuild iOS build — succeeded
  • react-native-iap: yarn typecheck + yarn test:library272/272 pass
  • expo-iap: bunx tsc --noEmit clean, bun run test269/269 pass
  • kmp-iap: ./gradlew :library:allTests with local apple source — BUILD SUCCESSFUL (build.gradle.kts reverted after verification)
  • godot-iap: swift buildBuild complete (no automated test suite)
  • Final 3rd-pass audit diffs every declared handler against implementation — 0 gaps across all 5 libraries

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • iOS: refund requests, StoreKit 2 entitlement/transaction queries, transaction verification/JWS, receipt retrieval, app transactions, sync, external-purchase custom link support (iOS 18.1+)
    • Android: consumable purchase token consumption
    • Exposed iOS-specific receipt validation and promoted-purchase/deep-link helpers across platforms
  • Documentation

    • Added SDK parity verification guidance and per-library checklist to prevent type-to-implementation mismatches

Closes #104 (Flutter `beginRefundRequestIOS` declared but not implemented),
plus every other instance of the same bug pattern found via a systematic
types-to-bridge audit across the 5 wrapper libraries.

- packages/apple: add ObjC bridge for requestPurchaseOnPromotedProductIOS
  and deepLinkToSubscriptions so KMP cinterop can reach them.
- flutter_inapp_purchase: add beginRefundRequestIOS + 6 iOS query handlers
  (currentEntitlementIOS, latestTransactionIOS, isTransactionVerifiedIOS,
  getTransactionJwsIOS, getReceiptDataIOS, canPresentExternalPurchaseNoticeIOS)
  across Swift plugin, Dart impl, and QueryHandlers/MutationHandlers bundles;
  fix channel-name drift on syncIOS/subscriptionStatusIOS/getAppTransaction;
  wire verifyPurchase + 3 Android billing-program handlers into the bundle.
- kmp-iap: replace 5 UnsupportedOperationException stubs in iosMain with
  real ObjC bridge calls (beginRefundRequestIOS, syncIOS, getAllTransactionsIOS,
  requestPurchaseOnPromotedProductIOS, deepLinkToSubscriptions).
- expo-iap: export consumePurchaseAndroid (asymmetric with acknowledgePurchaseAndroid)
  and getStorefrontIOS deprecated alias.
- react-native-iap: export validateReceiptIOS deprecated alias.
- godot-iap: add 5 missing APIs (validate_receipt, validate_receipt_ios, and
  three ExternalPurchaseCustomLink iOS 18.1+ methods) across GDExtension Swift
  bridge and GDScript public API.
- knowledge: add SDK Parity Checklist to 04-platform-packages.md with a
  5-layer table per library, platform-suffix rules, four observed failure
  patterns, and a grep-based audit command so future schema additions do
  not create phantom interfaces again.

After this PR every handler declared in each library's generated types has
a complete runtime path (public API + native bridge + bundle wiring where
applicable). Verified: packages/apple 87 tests, Flutter 257, RN-IAP 272,
expo-iap 269, kmp-iap allTests, godot-iap swift build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan hyochan added cross-platform Cross-platform (both Android & iOS) 📖 documentation Improvements or additions to documentation 🛠 bugfix All kinds of bug fixes labels Apr 24, 2026
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Warning

Rate limit exceeded

@hyochan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 5 seconds before requesting another review.

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 20 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a188531-2c34-4037-ad5a-07c982a6477c

📥 Commits

Reviewing files that changed from the base of the PR and between 50e3561 and dff9a3a.

📒 Files selected for processing (4)
  • .claude/commands/review-pr.md
  • libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
📝 Walkthrough

Walkthrough

Implements multiple iOS in‑app purchase APIs across platform SDKs (Flutter, Godot, KMP, React Native, Expo), adds Android consumePurchase helper, refactors Flutter syncIOS to call native sync directly, expands tests, and adds documentation and pre‑work SDK‑parity verification guidance.

Changes

Cohort / File(s) Summary
Documentation & Pre‑work
CLAUDE.md, knowledge/internal/04-platform-packages.md, .claude/commands/review-pr.md, packages/docs/src/pages/docs/updates/releases.tsx
Added SDK parity pre‑work instruction and formalized end‑to‑end handler parity checklist, failure modes, audit commands, and automated review re‑trigger steps; added release entry.
Expo IAP
libraries/expo-iap/src/modules/android.ts, libraries/expo-iap/src/modules/ios.ts
Added consumePurchaseAndroid mutation (Android) and deprecated getStorefrontIOS iOS query wrapper delegating to native module.
Flutter SDK & Tests
libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift, libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart, libraries/flutter_inapp_purchase/test/...
Added multiple iOS StoreKit2 APIs (refunds, entitlements, transactions, verification, receipts, external purchase notice); refactored syncIOS to native call; adjusted test mocks and added tests for new APIs.
Godot IAP
libraries/godot-iap/addons/godot-iap/godot_iap.gd, libraries/godot-iap/ios-gdextension/.../GodotIap.swift
Added request‑correlated async bridging, deprecated get_storefront_ios, receipt validation, and iOS 18.1+ External Purchase Custom Link flows with JSON/pending responses and availability gating.
KMP (Kotlin Multiplatform)
libraries/kmp-iap/library/src/iosMain/.../InAppPurchaseIOS.kt
Replaced UnsupportedOperationException stubs with real suspendCoroutine-backed OpenIAP async implementations for promoted purchase, deep link, refund, sync, and transaction queries.
React Native IAP
libraries/react-native-iap/src/index.ts
Added validateReceiptIOS exported alias that asserts iOS platform and delegates to shared validateReceipt.
Apple / ObjC Bindings
packages/apple/Sources/OpenIapModule+ObjC.swift
Added ObjC-exposed wrappers for promoted-product purchase (deprecated) and deepLinkToSubscriptions with ObjC completion handlers.
Other
libraries/godot-iap/..., various tests updated
Minor test text/format tweaks and additional platform gating/expectation updates across test suites.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested labels

📱 iOS, 🤖 android, flutter_inapp_purchase, react-native-iap, expo-iap, kmp-iap, ❄️ types

Poem

🐰
I hopped through code both near and far,
Wired StoreKit, sync, and refund star,
From Swift to Dart and Kotlin too,
Types aligned — the bridge is true.
A carrot cheer for parity new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: wiring every type-declared handler across all wrapper SDKs, which is the core objective of this multi-package PR.
Linked Issues check ✅ Passed The PR fully satisfies #104 by implementing beginRefundRequestIOS in Flutter, and extends the fix across all wrapper SDKs (expo, react-native, godot, kmp) to wire all type-declared handlers end-to-end.
Out of Scope Changes check ✅ Passed All changes are focused on implementing declared-but-unimplemented handlers and establishing SDK parity across wrapper libraries, with supporting documentation and test updates aligned to the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sdk-parity-all-libraries

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a critical SDK Parity Checklist to the internal documentation and implements a wide range of missing platform-specific handlers across the Expo, Flutter, Godot, KMP, and React Native libraries to ensure full end-to-end API support. Technical feedback identifies a high-severity deadlock risk in the Godot iOS implementation caused by blocking the main thread with a semaphore while awaiting asynchronous tasks. Additionally, redundant MainActor isolation wrappers were noted in the Flutter iOS plugin, which should be simplified for better readability.

Comment thread libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift Outdated
Comment thread libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift Outdated
After each fix batch is pushed, the skill now re-requests Copilot review
and posts /gemini review so the next automated pass starts immediately.
It then schedules a wake-up in ~480 seconds via ScheduleWakeup to re-enter
/review-pr and pick up any new unresolved threads, stopping only when the
PR is clean or the same finding repeats twice in a row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an SDK Parity Checklist to the internal documentation and implements several missing API handlers across the Expo, Flutter, Godot, KMP, and React Native libraries, focusing on StoreKit 2 features like entitlements and refund requests. Feedback highlights a missing method channel case in the Flutter iOS plugin, a thread-blocking semaphore in the Godot iOS extension that risks UI freezes, and incorrect platform logic in Flutter that unnecessarily excludes macOS from certain APIs. Additionally, the Godot GDScript implementation should be updated to use internal awaits for consistency with other asynchronous methods.

Comment thread libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift Outdated
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Lists the upcoming patch bumps (openiap-apple 2.1.4, react-native-iap
15.2.3, expo-iap 4.2.3, flutter_inapp_purchase 9.2.3, kmp-iap 2.2.3,
godot-iap 2.2.3) and summarizes the per-library handler additions
shipped with PR #105.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart (1)

699-712: syncIOS silently skips macOS despite being wired as a cross-Apple helper.

The new check if (!_platform.isIOS || _platform.isMacOS) still excludes macOS from actually calling native syncIOS, even though StoreKit 2 sync() is supported on macOS and the rest of the file treats isIOS (line 77) as "iOS or macOS". This keeps the pre-existing behavior, so I'm not flagging it as a regression of this PR — but while you're here re-plumbing syncIOS (and the new StoreKit‑2 iOS query handlers below use the same pattern), consider unifying on the isIOS getter so macOS clients get real results instead of a silent false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart` around
lines 699 - 712, The syncIOS handler currently returns early for macOS because
it uses the condition "if (!_platform.isIOS || _platform.isMacOS)"; change it to
rely on the unified isIOS getter (i.e., only check "if (!_platform.isIOS)") so
macOS is treated as an Apple platform and will call
_channel.invokeMethod<bool>('syncIOS'); also update any other StoreKit‑2 query
handlers using the same "isIOS || isMacOS" pattern to use the single
_platform.isIOS check (referencing the syncIOS function name and the
_platform.isIOS/_platform.isMacOS symbols to locate the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@knowledge/internal/04-platform-packages.md`:
- Around line 163-165: The KMP grep uses a recursive glob that fails in some
shells: replace the problematic pattern
"libraries/*/library/src/commonMain/kotlin/**/Types.kt" in the echo/rg command
with an explicit directory pattern that will match the generated Types.kt files
(for example "libraries/*/library/src/commonMain/kotlin/*/Types.kt" or enumerate
subdirs as needed), or alternatively use a find-based expression (e.g., find
libraries -path "*/library/src/commonMain/kotlin/*/Types.kt" -print0 | xargs -0
rg -n "$NAME") so the audit reliably locates the KMP-generated Types.kt files.

In `@libraries/expo-iap/src/modules/android.ts`:
- Around line 135-155: The consumePurchaseAndroid function currently returns
true for unrecognized native payloads, which can hide bridge regressions; update
the post-native-result handling (in consumePurchaseAndroid which calls
ExpoIapModule.consumePurchaseAndroid) so that instead of defaulting to true it
throws a descriptive Error (including the unexpected payload via
JSON.stringify(result)) or returns a failing value, ensuring callers fail fast
and logs surface the unexpected shape; keep the existing branches for boolean,
record.success, and record.responseCode, and add the new throw/error path as the
final fallback.

In `@libraries/godot-iap/addons/godot-iap/godot_iap.gd`:
- Around line 908-922: The doc comments for
get_external_purchase_custom_link_token_ios (and similarly validate_receipt_ios)
are incorrect: they claim an asynchronous result via the products_fetched signal
and a "Pending status string," but the implementation synchronously parses the
native JSON and returns a Types.ExternalPurchaseCustomLinkTokenResultIOS or
null; update the function docstrings to describe the actual synchronous
behavior, remove any mention of products_fetched or pending string, and clearly
state the return type (Types.ExternalPurchaseCustomLinkTokenResultIOS | null)
and that errors yield null so callers should handle a null return.
- Around line 863-869: get_storefront_ios() currently returns the raw JSON
string from _native_plugin.call("getStorefrontIOS") instead of extracting the
country code; update get_storefront_ios to mirror get_storefront() by calling
_native_plugin.call("getStorefrontIOS"), parsing the JSON (e.g. JSON.parse or
equivalent), checking for success and a "storefront" key, and returning that
storefront value (ISO alpha-2) or an empty string on any parse/failure;
reference the existing get_storefront() logic for exact parsing and error checks
and ensure checks for _native_plugin and _platform == "iOS" remain in place.

In `@libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift`:
- Around line 1129-1148: The method isEligibleForExternalPurchaseCustomLinkIOS
currently blocks the Godot thread using DispatchSemaphore.wait; instead refactor
it to use the async "signal/pending" pattern used by the token/notice methods:
start an asynchronous Task that calls
openIap.isEligibleForExternalPurchaseCustomLinkIOS(), store the result in a
shared state/flag (e.g. a pending result property) and emit the existing Godot
signal or resolve a pending promise back to Godot when complete, and return
immediately (no semaphore wait); update
isEligibleForExternalPurchaseCustomLinkIOS to return without blocking and ensure
error handling (catch from openIap) sets the pending result to false and emits
the same response path as the other async methods so the engine thread is never
blocked.
- Around line 1129-1223: Update the availability checks to match the underlying
OpenIapModule requirement: change the macOS version in the `#available` guards
from macOS 15.0 to macOS 15.1 in the three functions
isEligibleForExternalPurchaseCustomLinkIOS(),
getExternalPurchaseCustomLinkTokenIOS(tokenType:), and
showExternalPurchaseCustomLinkNoticeIOS(noticeType:). Locate the `#available`(...)
lines inside those methods and replace macOS 15.0 with macOS 15.1 so the wrapper
only enables the async calls when the underlying API is actually available.

In `@libraries/react-native-iap/src/index.ts`:
- Around line 1829-1840: The platform property check in validateReceiptIOS is
invalid because validateReceipt's iOS payload doesn't include result.platform,
causing false errors; remove or replace the (result as {platform?:
string}).platform !== 'ios' check and instead trust the earlier Platform.OS ===
'ios' guard (or perform a lightweight shape check of iOS-specific fields if you
need runtime validation), then return the casted VerifyPurchaseResultIOS from
validateReceiptIOS; update references to validateReceipt and validateReceiptIOS
only—do not introduce breaking changes to the public API.

---

Nitpick comments:
In `@libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart`:
- Around line 699-712: The syncIOS handler currently returns early for macOS
because it uses the condition "if (!_platform.isIOS || _platform.isMacOS)";
change it to rely on the unified isIOS getter (i.e., only check "if
(!_platform.isIOS)") so macOS is treated as an Apple platform and will call
_channel.invokeMethod<bool>('syncIOS'); also update any other StoreKit‑2 query
handlers using the same "isIOS || isMacOS" pattern to use the single
_platform.isIOS check (referencing the syncIOS function name and the
_platform.isIOS/_platform.isMacOS symbols to locate the code).
🪄 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: 7c4f7c01-51d0-417d-a082-5399d239dae3

📥 Commits

Reviewing files that changed from the base of the PR and between ffa9986 and 1cc1a9d.

📒 Files selected for processing (15)
  • CLAUDE.md
  • knowledge/internal/04-platform-packages.md
  • libraries/expo-iap/src/modules/android.ts
  • libraries/expo-iap/src/modules/ios.ts
  • libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart
  • libraries/flutter_inapp_purchase/test/ios_methods_test.dart
  • libraries/flutter_inapp_purchase/test/ios_module_methods_test.dart
  • libraries/flutter_inapp_purchase/test/subscription_handlers_test.dart
  • libraries/godot-iap/addons/godot-iap/godot_iap.gd
  • libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/react-native-iap/src/index.ts
  • packages/apple/Sources/OpenIapModule+ObjC.swift

Comment thread knowledge/internal/04-platform-packages.md
Comment thread libraries/expo-iap/src/modules/android.ts
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Comment thread libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift Outdated
Comment thread libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
Comment thread libraries/react-native-iap/src/index.ts
- rn-iap validateReceiptIOS: drop bogus result.platform check that always
  threw because the iOS payload does not carry a `platform` field.
- flutter plugin: add verifyPurchase method-channel case aliased to the
  validateReceiptIOS handler so the MutationHandlers bundle's
  verifyPurchase call works on iOS.
- flutter plugin: switch the six new iOS query handlers' platform
  guards from `!_platform.isIOS || _platform.isMacOS` to `!isIOS` so
  macOS (where the iOS getter is true) is correctly included — these
  StoreKit 2 APIs are available on macOS 12+.
- flutter plugin: drop the redundant `await MainActor.run` inside the
  new `Task { @mainactor in }` blocks (the whole task is already on
  MainActor).
- expo-iap consumePurchaseAndroid: throw with the unexpected payload
  instead of silently returning true when the native response is an
  unknown shape.
- godot-iap GDScript: parse the JSON from getStorefrontIOS into the
  country code instead of stringifying the whole dict; switch
  is_eligible_for_external_purchase_custom_link_ios,
  get_external_purchase_custom_link_token_ios,
  show_external_purchase_custom_link_notice_ios, and validate_receipt_ios
  to `await products_fetched` so callers get the result inline without
  subscribing to the signal by hand.
- godot-iap Swift: stop blocking the Godot main thread with
  DispatchSemaphore in isEligibleForExternalPurchaseCustomLinkIOS —
  return a pending status and emit through productsFetched like the
  sibling token/notice calls. Also correct the #available gate from
  macOS 15.0 to 15.1 (matches the OpenIapModule availability).
- knowledge parity checklist: replace the broken recursive glob
  `libraries/*/library/src/commonMain/kotlin/**/Types.kt` with an
  explicit kmp-iap directory path so the audit command actually finds
  the generated KMP types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive 'SDK Parity' patch to ensure all GraphQL-declared handlers are fully implemented across all wrapper libraries, including React Native, Expo, Flutter, KMP, and Godot. It introduces an SDK Parity Checklist in the internal knowledge base to prevent 'phantom interfaces' where APIs are declared but not implemented. Key updates include adding missing iOS handlers in Flutter, replacing stubs in KMP with native bridge calls, and providing deprecated aliases for backward compatibility. Feedback focuses on the Godot implementation, specifically regarding logic errors where asynchronous native calls are handled synchronously and a race condition caused by multiple asynchronous operations sharing a single signal.

Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd Outdated
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd Outdated
@hyochan hyochan requested a review from Copilot April 24, 2026 19:13
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

After pushing a fix batch, the skill now re-requests Copilot, posts
/gemini review, and posts @coderabbitai review so every automated
reviewer wired into this repo runs against the new commit before the
next 8-minute polling wake-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Apr 24, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Wires previously type-declared-but-unimplemented handlers end-to-end across multiple wrapper SDKs (Flutter, KMP, Expo, React Native, Godot, and Apple ObjC bridge) and documents an audit workflow to prevent parity regressions.

Changes:

  • Adds missing platform bridges / exports / handler-bundle wiring so generated type handlers have working runtime implementations across SDKs.
  • Fixes Flutter iOS channel-name drift (syncIOS, subscriptionStatusIOS, getAppTransactionIOS) and adds test coverage for newly-wired handlers.
  • Adds internal documentation (“SDK Parity Checklist”) and updates contributor guidance to enforce parity checks.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/docs/src/pages/docs/updates/releases.tsx Adds release-note entry describing the SDK parity patch and links to checklist/PR.
packages/apple/Sources/OpenIapModule+ObjC.swift Adds new ObjC completion-style bridge methods for KMP cinterop.
libraries/react-native-iap/src/index.ts Exports deprecated validateReceiptIOS alias for schema parity.
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt Replaces iOS stubs with real ObjC bridge calls (refund, sync, transactions, promoted purchase, deep link).
libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift Adds missing callable APIs (receipt validation alias + ExternalPurchaseCustomLink methods).
libraries/godot-iap/addons/godot-iap/godot_iap.gd Exposes new GDScript APIs (storefront alias, receipt validation wrappers, external purchase custom link methods).
libraries/flutter_inapp_purchase/test/subscription_handlers_test.dart Minor test formatting update for subscription handler wiring assertion.
libraries/flutter_inapp_purchase/test/ios_module_methods_test.dart Updates syncIOS mock + test description to reflect AppStore.sync behavior.
libraries/flutter_inapp_purchase/test/ios_methods_test.dart Adds tests for newly added iOS query/mutation handlers.
libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart Updates sync/restore tests to use syncIOS channel and new error-wrapping behavior.
libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart Adds new iOS handler getters, fixes syncIOS implementation, and wires handlers into generated bundles.
libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift Adds Swift method-channel cases and implementations for new iOS handlers + channel alias handling.
libraries/expo-iap/src/modules/ios.ts Adds deprecated getStorefrontIOS alias export for schema parity.
libraries/expo-iap/src/modules/android.ts Exports consumePurchaseAndroid with response normalization.
knowledge/internal/04-platform-packages.md Adds SDK parity checklist + audit command and failure-mode documentation.
CLAUDE.md Links contributors to the SDK parity checklist as required pre-work.
.claude/commands/review-pr.md Updates internal review workflow to re-request automated reviews after fix batches.
Comments suppressed due to low confidence (1)

libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift:276

  • The Swift method-channel handler maps "verifyPurchase" to validateReceiptIOS(productId:), but that payload does not include __typename. On the Dart side VerifyPurchaseResult.fromJson requires __typename (e.g., "VerifyPurchaseResultIOS"), so FlutterInappPurchase.verifyPurchase() will throw on iOS when parsing the response. Please implement a real verifyPurchase native path (calling OpenIapModule.shared.verifyPurchase(...) and serializing via the same encoder used elsewhere) or at minimum add the expected __typename field to the returned dictionary for the iOS result.
        case "validateReceiptIOS", "verifyPurchase":
            guard let args = call.arguments as? [String: Any] else {
                let code: ErrorCode = .developerError
                result(FlutterError(code: code.rawValue, message: "arguments required", details: nil))
                return
            }
            // Support new API: { apple: { sku: "..." } } or legacy { sku: "..." }
            let sku: String
            if let appleOptions = args["apple"] as? [String: Any],
               let appleSku = appleOptions["sku"] as? String {
                sku = appleSku
            } else if let legacySku = args["sku"] as? String {
                // Backwards compatibility with legacy API
                sku = legacySku
            } else {
                let code: ErrorCode = .developerError
                result(FlutterError(code: code.rawValue, message: "apple.sku required", details: nil))
                return
            }
            validateReceiptIOS(productId: sku, result: result)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart Outdated
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart Outdated
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd Outdated
- Tag every async productsFetched emit in GodotIap.swift with a method
  key (getStorefrontIOS, validateReceiptIOS,
  isEligibleForExternalPurchaseCustomLinkIOS,
  getExternalPurchaseCustomLinkTokenIOS,
  showExternalPurchaseCustomLinkNoticeIOS). Also emit on the error path
  of getStorefrontIOS so callers are not left hanging.
- In godot_iap.gd add a private _await_products_fetched_for(method)
  helper that loops await until the emit matching the requested method
  arrives. Every new async GDScript wrapper routes through this helper,
  so two concurrent async calls no longer race on the shared signal.
- Make get_storefront_ios() actually wait for the native async result
  and return the resolved storefront string instead of trying to parse
  the synchronous "pending" placeholder.
- Fix validate_receipt()'s Android path to call _verify_purchase_raw
  synchronously and wrap the dict in Types.VerifyPurchaseResultAndroid
  (matching the iOS path's typed return).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive "SDK parity patch" across all wrapper libraries (React Native, Expo, Flutter, Godot, and KMP) to ensure all type-declared handlers have functional runtime implementations, effectively addressing "phantom interface" bugs. It introduces a critical SDK Parity Checklist in the internal documentation and updates the review-pr command workflow with automated reviewer triggers. Feedback identifies several improvement opportunities: expanding macOS support in the Godot wrapper for storefront and receipt validation, ensuring consistent error wrapping into PurchaseError within the Flutter library, and addressing a potential race condition in the Godot signal-awaiting helper when handling concurrent requests.

Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd Outdated
- flutter_inapp_purchase: revert the six new "…IOS" query handler
  guards from `!isIOS` back to `!_platform.isIOS || _platform.isMacOS`
  to match the existing iOS-only pattern in the rest of the file
  (Copilot). macOS inclusion via the `isIOS` getter was inconsistent
  with every other iOS-suffixed handler in this plugin.
- .claude/commands/review-pr.md: teach the skill to auto-resolve
  threads that GitHub marks `isOutdated: true` and threads whose last
  comment is already from us, so we don't leave stale entries open
  every polling cycle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive SDK parity patch to ensure all GraphQL-declared handlers are implemented across the five wrapper libraries, addressing issue #104. Key changes include adding missing native bridge implementations in Flutter, KMP, and Godot, fixing channel-name drift, and introducing an SDK Parity Checklist in the internal documentation. Feedback highlights a potential race condition in the Godot signal multiplexing logic and an inconsistency in iOS platform checks within the Flutter library.

Comment thread libraries/godot-iap/addons/godot-iap/godot_iap.gd Outdated
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
…atches

- godot-iap Swift: each async @callable now mints a UUID requestId,
  includes it in the productsFetched emit dict, and returns it in the
  synchronous "pending" JSON response so the caller can match its own
  emit later.
- godot-iap GDScript: add _parse_request_id helper + extend
  _await_products_fetched_for(method, request_id) to filter on both
  fields. Two concurrent calls to the same method (e.g. two
  validate_receipt_ios with different SKUs) no longer race — both wake
  on every emit, but only the one whose requestId matches returns.
- flutter_inapp_purchase: add a generic `catch` fallback on syncIOS and
  all six new iOS query handlers (currentEntitlementIOS,
  latestTransactionIOS, isTransactionVerifiedIOS, getTransactionJwsIOS,
  getReceiptDataIOS, canPresentExternalPurchaseNoticeIOS) so non-
  PlatformException errors get wrapped into PurchaseError consistently
  with the rest of the library.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive SDK parity patch across all wrapper libraries (React Native, Expo, Flutter, KMP, and Godot) to ensure that every handler declared in the generated types has a corresponding runtime implementation, resolving issue #104. Key changes include the addition of an SDK Parity Checklist to the internal knowledge base, the replacement of UnsupportedOperationException stubs in KMP, and the implementation of missing iOS query handlers in Flutter and Godot. Feedback was provided regarding the validateReceiptIOS alias in React Native, suggesting that the platform check be expanded to include macOS support.

Comment thread libraries/react-native-iap/src/index.ts
@hyochan hyochan requested a review from Copilot April 24, 2026 19:49
@hyochan hyochan review requested due to automatic review settings April 24, 2026 19:50
Straight POST to /requested_reviewers returns HTTP 201 but silently
leaves the list empty when Copilot already submitted a review on an
earlier commit — the API treats an already-submitted reviewer as
idempotent and refuses to re-add them. Work around by DELETEing the
pending reviewer first, sleeping, then POSTing, and verify Copilot
actually ended up in requested_reviewers so we can warn (instead of
claiming success silently) when the manual UI "Re-request review"
button is the only remaining option.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart (1)

1779-1805: ⚠️ Potential issue | 🔴 Critical

Fix iOS native payload to include __typename discriminator.

The iOS native validateReceiptIOS handler returns a payload without the required __typename field that VerifyPurchaseResult.fromJson() expects for union type discrimination. This will crash at runtime with ArgumentError('Unknown __typename for VerifyPurchaseResult: null') on every iOS verifyPurchase() call.

Android native correctly adds "__typename": "VerifyPurchaseResultAndroid" (line 1088 of AndroidInappPurchasePlugin.kt), but iOS native (lines 820–830 of FlutterInappPurchasePlugin.swift) only returns "platform": "ios" without the discriminator.

Add "__typename": "VerifyPurchaseResultIOS" to the iOS payload construction, matching the Android pattern:

Suggested fix location
// ios/Classes/FlutterInappPurchasePlugin.swift, lines 820-827
var payload: [String: Any?] = [
    "__typename": "VerifyPurchaseResultIOS",  // ADD THIS LINE
    "isValid": res.isValid,
    "receiptData": res.receiptData,
    "jwsRepresentation": res.jwsRepresentation,
    "purchaseToken": res.jwsRepresentation,
    "platform": "ios"
]

Apply the same fix to the macOS implementation at macos/Classes/FlutterInappPurchasePlugin.swift lines 622–629.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart` around
lines 1779 - 1805, The iOS/macOS native receipt validation handlers are
returning payloads missing the GraphQL union discriminator, causing
VerifyPurchaseResult.fromJson to fail; update the
validateReceiptIOS/validateReceiptMacOS payload construction inside
FlutterInappPurchasePlugin (the native handler that builds the payload returned
to Dart) to include "__typename": "VerifyPurchaseResultIOS" (for iOS) and
"__typename": "VerifyPurchaseResultMacOS" (for macOS) alongside existing keys
like "isValid", "receiptData", "jwsRepresentation", "purchaseToken", and
"platform" so the Dart VerifyPurchaseResult.fromJson can discriminate the union
properly.
🧹 Nitpick comments (2)
libraries/expo-iap/src/modules/android.ts (1)

167-187: Consider aligning acknowledgePurchaseAndroid fallback with the new behavior.

acknowledgePurchaseAndroid still has the original return true; fallback (line 186) that consumePurchaseAndroid just replaced with a throw. Since both wrap the same native response-normalization pattern, keeping them divergent means an unknown acknowledge payload will silently report success while the consume variant fails fast. Out of scope for this PR, but worth a follow-up for consistency.

As per coding guidelines: Handle errors explicitly and avoid silent failures or unhandled promise rejections.

♻️ Suggested follow-up diff
-  return true;
+  throw new Error(
+    `acknowledgePurchaseAndroid returned an unexpected response payload: ${JSON.stringify(
+      result,
+    )}`,
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libraries/expo-iap/src/modules/android.ts` around lines 167 - 187, The
acknowledgePurchaseAndroid function currently falls back to returning true on
unknown native responses, which can silently mask failures; update
acknowledgePurchaseAndroid to mirror consumePurchaseAndroid's behavior by
throwing an explicit error when the result is neither a boolean nor a recognized
object shape (no valid record.success or responseCode), using the same error
message style and referencing ExpoIapModule's response for context so callers
fail fast instead of assuming success.
libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift (1)

1111-1115: Align validateReceiptIOS with codebase pattern: use OpenIapSerialization for VerifyPurchaseProps decoding.

The sibling verifyPurchase (line 962) and all other iOS validation implementations across the codebase (react-native-iap, flutter_inapp_purchase, expo-iap) decode via OpenIapSerialization.verifyPurchaseProps(from:), while validateReceiptIOS uniquely uses JSONDecoder().decode() directly. Even though both work for a Codable struct, using the serialization helper maintains consistency with the established pattern and ensures any future normalization or validation logic in the helper applies uniformly.

Suggested refactor
do {
    guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
        throw NSError(domain: "GodotIap", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON structure"])
    }
    let props = try OpenIapSerialization.verifyPurchaseProps(from: dict)
    let result = try await self.openIap.validateReceiptIOS(props)
} catch {
    // error handling
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift` around
lines 1111 - 1115, Replace the direct JSONDecoder decoding in the
validateReceiptIOS call with the project's OpenIapSerialization path: convert
propsJson to a JSON object/dictionary (via JSONSerialization.jsonObject), pass
that dictionary into OpenIapSerialization.verifyPurchaseProps(from:) to obtain a
VerifyPurchaseProps instance, and then call
self.openIap.validateReceiptIOS(props); keep error handling consistent with
nearby verifyPurchase usage. Reference symbols: validateReceiptIOS,
VerifyPurchaseProps, OpenIapSerialization.verifyPurchaseProps(from:), and
self.openIap.validateReceiptIOS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/commands/review-pr.md:
- Around line 165-166: The jq auto-resolve predicate 'select(.isOutdated == true
or .comments.nodes[-1].author.login == "hyochan") | .id' is too permissive and
can close threads when the author merely replied; change it to only auto-resolve
on isOutdated == true (i.e., 'select(.isOutdated == true) | .id') or, if you
want author-triggered auto-resolve, require an explicit marker in the author’s
latest comment (e.g., check .comments.nodes[-1].body for a token like
"[auto-resolve]" or "[resolved]" before selecting the thread id); apply the same
change to the second occurrence mentioned (the block around the other predicate
at the later lines).

In
`@libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift`:
- Around line 647-659: The catch-all in beginRefundRequestIOS (and the other new
helpers currentEntitlementIOS, latestTransactionIOS, isTransactionVerifiedIOS,
getTransactionJwsIOS, getReceiptDataIOS, subscriptionStatusIOS,
getAppTransactionIOS) currently maps every error to ErrorCode.serviceError and
drops native PurchaseError codes; update each function to mirror the
fetchProducts pattern: catch PurchaseError first, extract its associated native
code (map it to the plugin ErrorCode or use its rawValue/message as
appropriate), call result(FlutterError(...)) with that preserved code and
details (error.localizedDescription or the PurchaseError payload), then have a
final catch that maps unknown errors to .serviceError. Reference functions:
beginRefundRequestIOS, currentEntitlementIOS, latestTransactionIOS,
isTransactionVerifiedIOS, getTransactionJwsIOS, getReceiptDataIOS,
subscriptionStatusIOS, getAppTransactionIOS and the fetchProducts error-handling
block for implementation guidance.

In `@libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift`:
- Around line 1179-1182: The unsupported-OS branch in the methods that return
"{\"status\": \"unsupported\"}" must still include the same requestId and emit
the productsFetched signal so GDScript awaiters don't deadlock; update each
affected method (the ones that currently return "unsupported" and use the local
requestId variable) to return "{\"status\": \"unsupported\", \"requestId\":
\"\(requestId)\"}" and call emitSignal("productsFetched", methodName, false,
"unsupported", requestId) (or the existing signal emission helper) so the async
contract is fulfilled across iOS/macOS/tvOS versions.

In `@packages/docs/src/pages/docs/updates/releases.tsx`:
- Around line 128-137: Update the broken fragment in the anchor href used in the
SDK Parity Checklist link: locate the anchor with href
"https://github.com/hyodotdev/openiap/blob/main/knowledge/internal/04-platform-packages.md#sdk-parity-checklist-critical--prevents-declared-but-not-implemented"
and change the fragment portion to use a single dash so it becomes
"#sdk-parity-checklist-critical-prevents-declared-but-not-implemented", ensuring
the GitHub heading link resolves correctly.

---

Outside diff comments:
In `@libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart`:
- Around line 1779-1805: The iOS/macOS native receipt validation handlers are
returning payloads missing the GraphQL union discriminator, causing
VerifyPurchaseResult.fromJson to fail; update the
validateReceiptIOS/validateReceiptMacOS payload construction inside
FlutterInappPurchasePlugin (the native handler that builds the payload returned
to Dart) to include "__typename": "VerifyPurchaseResultIOS" (for iOS) and
"__typename": "VerifyPurchaseResultMacOS" (for macOS) alongside existing keys
like "isValid", "receiptData", "jwsRepresentation", "purchaseToken", and
"platform" so the Dart VerifyPurchaseResult.fromJson can discriminate the union
properly.

---

Nitpick comments:
In `@libraries/expo-iap/src/modules/android.ts`:
- Around line 167-187: The acknowledgePurchaseAndroid function currently falls
back to returning true on unknown native responses, which can silently mask
failures; update acknowledgePurchaseAndroid to mirror consumePurchaseAndroid's
behavior by throwing an explicit error when the result is neither a boolean nor
a recognized object shape (no valid record.success or responseCode), using the
same error message style and referencing ExpoIapModule's response for context so
callers fail fast instead of assuming success.

In `@libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift`:
- Around line 1111-1115: Replace the direct JSONDecoder decoding in the
validateReceiptIOS call with the project's OpenIapSerialization path: convert
propsJson to a JSON object/dictionary (via JSONSerialization.jsonObject), pass
that dictionary into OpenIapSerialization.verifyPurchaseProps(from:) to obtain a
VerifyPurchaseProps instance, and then call
self.openIap.validateReceiptIOS(props); keep error handling consistent with
nearby verifyPurchase usage. Reference symbols: validateReceiptIOS,
VerifyPurchaseProps, OpenIapSerialization.verifyPurchaseProps(from:), and
self.openIap.validateReceiptIOS.
🪄 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: df002563-eb44-41b8-bec3-10a6bc8db0d7

📥 Commits

Reviewing files that changed from the base of the PR and between 1cc1a9d and 50e3561.

📒 Files selected for processing (9)
  • .claude/commands/review-pr.md
  • knowledge/internal/04-platform-packages.md
  • libraries/expo-iap/src/modules/android.ts
  • libraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/godot-iap/addons/godot-iap/godot_iap.gd
  • libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
  • libraries/react-native-iap/src/index.ts
  • packages/docs/src/pages/docs/updates/releases.tsx
✅ Files skipped from review due to trivial changes (1)
  • libraries/godot-iap/addons/godot-iap/godot_iap.gd
🚧 Files skipped from review as they are similar to previous changes (1)
  • knowledge/internal/04-platform-packages.md

Comment thread .claude/commands/review-pr.md Outdated
Comment thread libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
Comment thread packages/docs/src/pages/docs/updates/releases.tsx
- flutter_inapp_purchase plugin: add `catch let purchaseError as
  PurchaseError` before the generic catch on all nine new helpers
  (beginRefundRequestIOS, syncIOS, subscriptionStatusIOS,
  getAppTransactionIOS, currentEntitlementIOS, latestTransactionIOS,
  isTransactionVerifiedIOS, getTransactionJwsIOS, getReceiptDataIOS)
  so native codes like .skuNotFound / .transactionValidationFailed
  reach Flutter consumers instead of collapsing to .serviceError,
  matching the fetchProducts/requestPurchase pattern already in this
  file.
- godot-iap Swift: on the unsupported-OS branch of
  isEligibleForExternalPurchaseCustomLinkIOS,
  getExternalPurchaseCustomLinkTokenIOS, and
  showExternalPurchaseCustomLinkNoticeIOS, mint the requestId before
  the #available gate, emit productsFetched with success=false /
  error="unsupported", and include requestId in the pending JSON
  response. Without this emit the GDScript awaiter would hang forever
  on older iOS/macOS/tvOS runtimes.
- .claude/commands/review-pr.md: tighten the auto-resolve sweep — only
  sweep threads GitHub marks isOutdated. "Last comment is from author"
  is a false-positive signal because reviewers often want to confirm
  the fix; leaving those threads open on the next round gives the
  reviewer a chance to push back or acknowledge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive SDK parity patch to ensure all GraphQL-declared handlers are fully implemented across the Flutter, React Native, Expo, Godot, and KMP wrapper libraries. It introduces a new SDK Parity Checklist in the internal documentation to prevent "phantom interface" bugs and updates the automated review workflows. Feedback indicates that several new iOS-specific methods in the Flutter library unnecessarily exclude macOS via platform checks, despite the underlying native implementations supporting it.

Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart Outdated
`ExternalPurchase.canPresent` and `ExternalPurchase.presentNoticeSheet`
ship at iOS 17.4 / macOS 14.4 / tvOS 17.4 / visionOS 1.1, not 18.2.
The Flutter Swift plugin's #available gates, the error messages, the
@available annotations on the private helpers, and the MARK comment
all said 18.2 — so callers were told the feature needed a newer OS
than it actually does. Update the Dart doc comment too so consumers
gate their UI on the real minimum.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

/gemini review

@hyochan
Copy link
Copy Markdown
Member Author

hyochan commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive SDK parity patch across the monorepo to resolve "phantom interface" issues where GraphQL-declared APIs lacked end-to-end implementation. It adds a critical SDK Parity Checklist to the internal documentation, implements numerous missing native bridge handlers for iOS and Android across Flutter, Expo, KMP, and Godot, and updates the documentation and automated PR review commands to include polling and outdated thread resolution. I have no feedback to provide.

@hyochan hyochan merged commit eb6ae9c into main Apr 24, 2026
14 checks passed
@hyochan hyochan deleted the fix/sdk-parity-all-libraries branch April 24, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠 bugfix All kinds of bug fixes cross-platform Cross-platform (both Android & iOS) 📖 documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing beginRefundRequest implementation in Flutter SDK

2 participants