fix: wire every type-declared handler across all wrapper SDKs#105
Conversation
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>
|
/gemini review |
|
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 20 minutes and 5 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 (4)
📝 WalkthroughWalkthroughImplements 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
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Possibly related PRs
Suggested labels
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 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 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.
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>
There was a problem hiding this comment.
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.
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>
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart (1)
699-712:syncIOSsilently skips macOS despite being wired as a cross-Apple helper.The new check
if (!_platform.isIOS || _platform.isMacOS)still excludes macOS from actually calling nativesyncIOS, even though StoreKit 2sync()is supported on macOS and the rest of the file treatsisIOS(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-plumbingsyncIOS(and the new StoreKit‑2 iOS query handlers below use the same pattern), consider unifying on theisIOSgetter so macOS clients get real results instead of a silentfalse.🤖 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
📒 Files selected for processing (15)
CLAUDE.mdknowledge/internal/04-platform-packages.mdlibraries/expo-iap/src/modules/android.tslibraries/expo-iap/src/modules/ios.tslibraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swiftlibraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dartlibraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dartlibraries/flutter_inapp_purchase/test/ios_methods_test.dartlibraries/flutter_inapp_purchase/test/ios_module_methods_test.dartlibraries/flutter_inapp_purchase/test/subscription_handlers_test.dartlibraries/godot-iap/addons/godot-iap/godot_iap.gdlibraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swiftlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktlibraries/react-native-iap/src/index.tspackages/apple/Sources/OpenIapModule+ObjC.swift
- 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>
|
/gemini review |
There was a problem hiding this comment.
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.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
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>
There was a problem hiding this comment.
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 sideVerifyPurchaseResult.fromJsonrequires__typename(e.g., "VerifyPurchaseResultIOS"), soFlutterInappPurchase.verifyPurchase()will throw on iOS when parsing the response. Please implement a realverifyPurchasenative path (callingOpenIapModule.shared.verifyPurchase(...)and serializing via the same encoder used elsewhere) or at minimum add the expected__typenamefield 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.
- 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>
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
- 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>
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
…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>
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
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>
There was a problem hiding this comment.
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 | 🔴 CriticalFix iOS native payload to include
__typenamediscriminator.The iOS native
validateReceiptIOShandler returns a payload without the required__typenamefield thatVerifyPurchaseResult.fromJson()expects for union type discrimination. This will crash at runtime withArgumentError('Unknown __typename for VerifyPurchaseResult: null')on every iOSverifyPurchase()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.swiftlines 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 aligningacknowledgePurchaseAndroidfallback with the new behavior.
acknowledgePurchaseAndroidstill has the originalreturn true;fallback (line 186) thatconsumePurchaseAndroidjust 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: AlignvalidateReceiptIOSwith codebase pattern: useOpenIapSerializationforVerifyPurchasePropsdecoding.The sibling
verifyPurchase(line 962) and all other iOS validation implementations across the codebase (react-native-iap, flutter_inapp_purchase, expo-iap) decode viaOpenIapSerialization.verifyPurchaseProps(from:), whilevalidateReceiptIOSuniquely usesJSONDecoder().decode()directly. Even though both work for aCodablestruct, 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
📒 Files selected for processing (9)
.claude/commands/review-pr.mdknowledge/internal/04-platform-packages.mdlibraries/expo-iap/src/modules/android.tslibraries/flutter_inapp_purchase/ios/Classes/FlutterInappPurchasePlugin.swiftlibraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dartlibraries/godot-iap/addons/godot-iap/godot_iap.gdlibraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swiftlibraries/react-native-iap/src/index.tspackages/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
- 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>
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
`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>
|
/gemini review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
Summary
Closes #104 — Flutter
beginRefundRequestIOSwas declared intypes.dartbut 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@objcbridge forrequestPurchaseOnPromotedProductIOSanddeepLinkToSubscriptionsso KMP's cinterop can reach them.flutter_inapp_purchaseQueryHandlers/MutationHandlersbundle wiring):beginRefundRequestIOS,currentEntitlementIOS,latestTransactionIOS,isTransactionVerifiedIOS,getTransactionJwsIOS,getReceiptDataIOS,canPresentExternalPurchaseNoticeIOS.syncIOS(end+init workaround → realAppStore.sync),subscriptionStatusIOS/getSubscriptionStatus,getAppTransactionIOS/getAppTransaction— Swift now accepts both names and the old bridge case was missing entirely.verifyPurchase,isBillingProgramAvailableAndroid,createBillingProgramReportingDetailsAndroid, andlaunchExternalLinkAndroid(adapter getter for named-param signature) into theMutationHandlersbundle.kmp-iapUnsupportedOperationExceptionstubs iniosMain/InAppPurchaseIOS.ktwith real ObjC bridge calls:beginRefundRequestIOS,syncIOS,getAllTransactionsIOS(stale TODO removed), plusrequestPurchaseOnPromotedProductIOSanddeepLinkToSubscriptionsusing the new bridge methods.expo-iapconsumePurchaseAndroid(previously asymmetric vsacknowledgePurchaseAndroid).getStorefrontIOSdeprecated alias (declared in schema).react-native-iapvalidateReceiptIOSdeprecated alias (declared in schema).godot-iapvalidate_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.mdUnsupportedOperationExceptionstub, channel-name drift, handler bundle omission), and a grep-based audit command so future schema additions can be validated mechanically.CLAUDE.mdlinks to the new checklist from the required pre-work.Test plan
packages/apple:swift buildclean,swift test— 87/87 passflutter_inapp_purchase:flutter analyzeclean,flutter test— 257/257 pass,xcodebuildiOS build — succeededreact-native-iap:yarn typecheck+yarn test:library— 272/272 passexpo-iap:bunx tsc --noEmitclean,bun run test— 269/269 passkmp-iap:./gradlew :library:allTestswith local apple source — BUILD SUCCESSFUL (build.gradle.kts reverted after verification)godot-iap:swift build— Build complete (no automated test suite)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation