feat: support local IAPKit receipt verification - #225
Conversation
|
Warning Review limit reached
Next review available in: 15 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThis PR adds configurable IAPKit verification endpoints across OpenIAP contracts, adapters, native bridges, and examples. It introduces Local (IAPKit) purchase flows, stricter URL validation, receipt E2E runbooks, smoke-server probes, and Godot framework packaging changes. ChangesIAPKit verification
Godot packaging
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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 updates the OpenIAP specification to version 2.3.1, introducing support for a custom baseUrl in IAPKit purchase verification to enable self-hosted or local testing. The changes span multiple client libraries (Expo, React Native, Flutter, KMP, Maui, Godot) and native packages (Android, iOS), adding the baseUrl property to verification parameters, updating native bridges, implementing robust HTTP(S) origin validation, and adding a 'Local (IAPKit)' option to example apps. E2E testing documentation, skills, and local server smoke tests have also been updated to support this workflow. As there are no review comments provided, I have no feedback to evaluate.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Physical Pixel 2 preview (no purchase): verification modes appear in the required order — Local (Device), Local (IAPKit), IAPKit, None (Skip) — and selecting Local (IAPKit) is reflected in the UI. openiap-iapkit-selector.mp4 |
There was a problem hiding this comment.
Code Review
This pull request implements support for a custom baseUrl in the IAPKit purchase verification provider across all platform packages and framework libraries (React Native, Expo, Flutter, Godot, KMP, MAUI, Apple, and Google), upgrading the OpenIAP Spec to version 2.3.1. This allows developers to route verification requests to self-hosted or local IAPKit servers. The updates include GraphQL schema modifications, native bridge forwarding, strict HTTP(S) origin validation, and comprehensive enhancements to the example apps (such as a new "Local (IAPKit)" option and selector modal) along with E2E test documentation and skills. There are no review comments to address, and I have no additional feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR extends the OpenIAP purchase-verification surface to support self-hosted / locally compiled IAPKit receipt verification by introducing an optional baseUrl (validated as an HTTP(S) origin) and wiring it end-to-end across native packages, framework bridges, examples, docs, and E2E guidance.
Changes:
- Add optional
baseUrlto theiapkitverification props (spec 2.3.1) and propagate it through generated SDK types, Apple/Google native implementations, and multiple framework bridges. - Update React Native + Expo Martie examples to include Local (IAPKit) as a distinct verification choice (in the required order) and ensure hosted IAPKit can intentionally omit a configured local URL.
- Harden
packages/kitcompiled-server smoke checks and add/extend skills + E2E documentation for a Martie local-receipt vertical; tighten Godot Apple framework installation and fail-closed load checks.
Reviewed changes
Copilot reviewed 69 out of 82 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/kit/scripts/smoke-server.sh | Harden compiled-server smoke by confirming port ownership via log marker and probing malformed POST /v1/purchase/verify for a 400. |
| packages/kit/README.md | Update smoke-server documentation to match the hardened probes and behavior. |
| packages/kit/CONVENTION.md | Update conventions doc for the new smoke-server readiness check and verify probe. |
| packages/gql/src/type.graphql | Add baseUrl to the GraphQL input schema with spec availability and deployment guidance. |
| packages/gql/src/generated/types.ts | Generated TS types include baseUrl on IAPKit verification props. |
| packages/gql/src/generated/Types.swift | Generated Swift types include baseUrl on IAPKit verification props. |
| packages/gql/src/generated/Types.kt | Generated Kotlin types include baseUrl on IAPKit verification props (JSON in/out). |
| packages/gql/src/generated/types.gd | Generated GDScript types include base_url mapping to/from baseUrl. |
| packages/gql/src/generated/types.dart | Generated Dart types include baseUrl (JSON in/out). |
| packages/gql/src/generated/Types.cs | Generated C# types include BaseUrl for IAPKit verification props. |
| packages/gql/package.json | Bump GQL package version to 2.3.1. |
| packages/google/openiap/src/test/java/dev/hyo/openiap/PurchaseVerificationValidatorTest.kt | Add/extend Android native tests for defaulting, normalization, and rejection of malformed baseUrl. |
| packages/google/openiap/src/main/java/dev/hyo/openiap/utils/PurchaseVerificationValidator.kt | Implement Android-side IAPKit endpoint resolution + strict origin validation before verifying. |
| packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt | Synced Google package Kotlin types include baseUrl. |
| packages/docs/src/pages/docs/types/verify-purchase-with-provider-props.tsx | Document the new baseUrl field and its constraints in the docs site. |
| packages/docs/package.json | Bump docs package version to 2.3.1. |
| packages/docs/openiap-versions.json | Update docs’ spec pin to 2.3.1. |
| packages/apple/Tests/OpenIapTests/VerifyPurchaseWithProviderTests.swift | Add iOS/macOS tests for default URL behavior, trimming, malformed override rejection, and ObjC selector parity. |
| packages/apple/Sources/OpenIapModule+ObjC.swift | Add an ObjC bridge selector that forwards baseUrl while preserving the legacy selector. |
| packages/apple/Sources/OpenIapModule.swift | Centralize and validate IAPKit verification URL construction via iapkitVerificationURL(baseUrl:). |
| packages/apple/Sources/Models/Types.swift | Synced Apple package Swift types include baseUrl. |
| openiap-versions.json | Bump monorepo spec pin to 2.3.1. |
| libraries/react-native-iap/src/vega-adapter.ts | Add robust origin-only URL parsing/validation for Kepler/Vega and use it for IAPKit verify requests. |
| libraries/react-native-iap/src/types.ts | Synced RN types include baseUrl. |
| libraries/react-native-iap/src/specs/RnIap.nitro.ts | Extend Nitro contract to include baseUrl for iapkit props. |
| libraries/react-native-iap/src/tests/vega-adapter.test.ts | Add parameterized tests for valid/invalid baseUrl, including IPv6 and Kepler URL limitations. |
| libraries/react-native-iap/src/tests/index.test.ts | Extend public API tests to cover baseUrl in verify payload shape. |
| libraries/react-native-iap/src/tests/iapkit-base-url-bridge.test.js | Ensure bridge parity by asserting baseUrl appears in the Nitro spec and native forwarding maps. |
| libraries/react-native-iap/ios/HybridRnIap.swift | Forward baseUrl through the iOS native payload mapping. |
| libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt | Forward baseUrl through the Android native payload mapping. |
| libraries/react-native-iap/example/src/utils/vegaRuntime.ts | Add helpers to require API key, resolve local-vs-hosted base URL, and ensure payloads omit local URL when hosted is selected. |
| libraries/react-native-iap/example/src/hooks/useVerificationMethod.ts | Expand verification method state to include iapkit-localhost and support Android modal selection. |
| libraries/react-native-iap/example/src/components/VerificationMethodSelectorModal.tsx | New Android-friendly modal UI that lists verification options in the required order. |
| libraries/react-native-iap/example/screens/SubscriptionFlow.tsx | Integrate Local (IAPKit) choice, route baseUrl appropriately, and wire selector modal. |
| libraries/react-native-iap/example/screens/PurchaseFlow.tsx | Integrate Local (IAPKit) choice, route baseUrl appropriately, and wire selector modal. |
| libraries/react-native-iap/example/README.md | Add concrete setup instructions for hosted vs local IAPKit verification and adb reverse guidance. |
| libraries/react-native-iap/example/.env.example | Clarify hosted vs local key requirements and base URL expectations. |
| libraries/react-native-iap/example/tests/utils/vegaRuntime.test.ts | Add tests for default method selection and stricter API key/baseUrl requirements. |
| libraries/react-native-iap/example/tests/screens/SubscriptionFlow.test.tsx | Add tests for correct default selection, method switching, and baseUrl forwarding/omission. |
| libraries/react-native-iap/example/tests/screens/PurchaseFlow.test.tsx | Add tests for correct ordering, routing via local server, and hosted omission of baseUrl. |
| libraries/maui-iap/src/OpenIap.Maui/Types.cs | Synced MAUI types include BaseUrl. |
| libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt | Forward baseUrl via the iOS ObjC bridge call. |
| libraries/kmp-iap/library/src/commonTest/kotlin/io/github/hyochan/kmpiap/VerificationTest.kt | Extend KMP tests to ensure baseUrl is serialized. |
| libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt | Synced KMP OpenIAP types include baseUrl (JSON in/out). |
| libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/IapkitBaseUrlBridgeTest.kt | Add a bridge regression test asserting Android forwards baseUrl. |
| libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt | Wire baseUrl into the Android-side IAPKit props payload. |
| libraries/godot-iap/scripts/install_apple_framework.sh | New helper to normalize Apple frameworks, preserve plist when possible, and enforce fail-closed macOS signing/rpath setup. |
| libraries/godot-iap/Makefile | Use the installer script for iOS/macOS frameworks and add a native extension load test to make test. |
| libraries/godot-iap/Example/tests/test_native_extension.gd | New macOS-only headless test to ensure the tracked native extension can load/instantiate. |
| libraries/godot-iap/android/src/test/java/dev/hyo/godotiap/GodotIapVerificationBridgeTest.kt | Add tests to ensure legacy and nested iapkit payloads include/pass baseUrl. |
| libraries/godot-iap/android/src/main/java/dev/hyo/godotiap/GodotIap.kt | Extract/extend provider-props normalization to include baseUrl for legacy maps. |
| libraries/godot-iap/addons/godot-iap/types.gd | Synced Godot addon types include base_url mapping to/from baseUrl. |
| libraries/godot-iap/addons/godot-iap/android/GodotIap.gdap | Update remote dependency pin for openiap-google to 2.3.1. |
| libraries/flutter_inapp_purchase/test/iapkit_base_url_bridge_test.dart | Add test asserting iOS/macos/Android native plugins forward baseUrl. |
| libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart | Add test asserting Dart API forwards baseUrl into the native payload. |
| libraries/flutter_inapp_purchase/macos/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift | Forward baseUrl in the macOS plugin iapkit map. |
| libraries/flutter_inapp_purchase/lib/types.dart | Synced Flutter types include baseUrl (JSON in/out). |
| libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart | Include baseUrl in the verifyPurchaseWithProvider method channel payload when set. |
| libraries/flutter_inapp_purchase/ios/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift | Forward baseUrl in the iOS plugin iapkit map. |
| libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt | Forward baseUrl in the Android plugin iapkit map. |
| libraries/expo-iap/src/vega-adapter.ts | Add origin-only base URL parsing/validation (Kepler-safe) and use it for IAPKit fetch requests. |
| libraries/expo-iap/src/types.ts | Synced Expo types include baseUrl. |
| libraries/expo-iap/src/tests/vega-adapter.test.ts | Add parameterized tests for valid/invalid base URLs, including IPv6 and Kepler URL limitations. |
| libraries/expo-iap/example/src/utils/vegaRuntime.ts | Add local-vs-hosted base URL resolver, method defaults, and stricter key requirements. |
| libraries/expo-iap/example/README.md | Add hosted vs local verification setup instructions and adb reverse guidance. |
| libraries/expo-iap/example/app/subscription-flow.tsx | Add Local (IAPKit) selection, route local baseUrl only for local mode, and update labels/messages. |
| libraries/expo-iap/example/app/purchase-flow.tsx | Add Local (IAPKit) selection, route local baseUrl only for local mode, and update labels/messages. |
| libraries/expo-iap/example/.env.example | Clarify hosted vs local key requirements and base URL expectations. |
| libraries/expo-iap/example/tests/vega-runtime.test.ts | Extend tests for default method selection and baseUrl omission/requirements. |
| libraries/expo-iap/example/tests/subscription-flow.test.tsx | Add tests for required verification option ordering and baseUrl forwarding behavior. |
| libraries/expo-iap/example/tests/purchase-flow.test.tsx | Add tests for required ordering and correct routing of local vs hosted IAPKit requests. |
| bun.lock | Sync lockfile metadata for the 2.3.1 package version bumps. |
| .codex/skills/review-self/SKILL.md | Reference the new Martie local-receipt E2E skill. |
| .codex/skills/iapkit-e2e-martie/SKILL.md | New detailed skill/runbook for the Martie Local (IAPKit) live receipt vertical, including safety gates. |
| .codex/skills/iapkit-e2e-martie/agents/openai.yaml | Agent metadata wiring for the new Martie E2E skill. |
| .claude/commands/e2e-tests.md | Add the local IAPKit Martie receipt vertical to the E2E matrix and document prerequisites/reporting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Code Review
This pull request introduces support for local and self-hosted IAPKit receipt verification across multiple framework libraries (including React Native, Expo, Flutter, Godot, KMP, and MAUI) by adding a 'baseUrl' property to the OpenIAP Spec (v2.3.1). The feedback points out several issues: the use of non-POSIX '[[' syntax in 'smoke-server.sh' which can cause syntax errors, a potential SSRF vulnerability in the IPv4 validation regex of the Vega adapters due to allowed leading zeros, and TypeScript type-widening issues in the example runtime helpers.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| sleep 0.25 | ||
| done | ||
|
|
||
| if [[ "$ready" -ne 1 ]]; then |
There was a problem hiding this comment.
There was a problem hiding this comment.
Not changing this: smoke-server.sh is explicitly a Bash executable (#!/usr/bin/env bash), and every repository invocation executes ./scripts/smoke-server.sh directly. It already relies on Bash-only BASH_SOURCE, pipefail, local, and multiple existing [[ expressions, so replacing this one condition would neither improve nor promise POSIX sh compatibility. bash -n passes.
| -H "Content-Type: application/json" \ | ||
| --data '{}' \ | ||
| "http://localhost:${PORT}${path}")" | ||
| if [[ "$code" != "$expected" ]]; then |
There was a problem hiding this comment.
Not changing this: smoke-server.sh is explicitly a Bash executable (#!/usr/bin/env bash), and every repository invocation executes ./scripts/smoke-server.sh directly. The script already uses Bash-only BASH_SOURCE, pipefail, local, and existing [[ expressions, so changing only this comparison would not make it POSIX sh compatible. bash -n passes.
| export function resolveIapkitVerificationBaseUrl( | ||
| method: 'iapkit-localhost' | 'iapkit', | ||
| configuredBaseUrl: string | null | undefined = getConfiguredIapkitBaseUrl(), | ||
| ): string | undefined { | ||
| if (method === 'iapkit') { | ||
| return undefined; | ||
| } | ||
|
|
||
| const baseUrl = configuredBaseUrl?.trim(); | ||
| if (!baseUrl) { | ||
| throw new Error( | ||
| 'EXPO_PUBLIC_IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | ||
| ); | ||
| } | ||
|
|
||
| return baseUrl; | ||
| } |
There was a problem hiding this comment.
The method parameter is typed as 'iapkit-localhost' | 'iapkit', but the callers in purchase-flow.tsx and subscription-flow.tsx pass currentVerificationMethod which is of the wider VerificationMethod type. This can cause TypeScript compilation errors due to type widening. Typing the parameter as VerificationMethod and checking method !== 'iapkit-localhost' avoids this issue.
| export function resolveIapkitVerificationBaseUrl( | |
| method: 'iapkit-localhost' | 'iapkit', | |
| configuredBaseUrl: string | null | undefined = getConfiguredIapkitBaseUrl(), | |
| ): string | undefined { | |
| if (method === 'iapkit') { | |
| return undefined; | |
| } | |
| const baseUrl = configuredBaseUrl?.trim(); | |
| if (!baseUrl) { | |
| throw new Error( | |
| 'EXPO_PUBLIC_IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | |
| ); | |
| } | |
| return baseUrl; | |
| } | |
| export function resolveIapkitVerificationBaseUrl( | |
| method: VerificationMethod, | |
| configuredBaseUrl: string | null | undefined = getConfiguredIapkitBaseUrl(), | |
| ): string | undefined { | |
| if (method !== 'iapkit-localhost') { | |
| return undefined; | |
| } | |
| const baseUrl = configuredBaseUrl?.trim(); | |
| if (!baseUrl) { | |
| throw new Error( | |
| 'EXPO_PUBLIC_IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | |
| ); | |
| } | |
| return baseUrl; | |
| } |
There was a problem hiding this comment.
Not changing this: the call occurs only in the else branch after ignore and local have both been excluded, so TypeScript narrows currentVerificationMethod to iapkit-localhost | iapkit. The strict Expo example typecheck passes. Widening the helper to VerificationMethod would hide invalid ignore/local calls and weaken its IAPKit-only contract.
| export function resolveIapkitVerificationBaseUrl( | ||
| method: 'iapkit-localhost' | 'iapkit', | ||
| configuredBaseUrl?: string | null, | ||
| ): string | undefined { | ||
| if (method === 'iapkit') { | ||
| return undefined; | ||
| } | ||
|
|
||
| const baseUrl = configuredBaseUrl?.trim(); | ||
| if (!baseUrl) { | ||
| throw new Error( | ||
| 'IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | ||
| ); | ||
| } | ||
|
|
||
| return baseUrl; | ||
| } |
There was a problem hiding this comment.
The method parameter is typed as 'iapkit-localhost' | 'iapkit', but the callers in PurchaseFlow.tsx and SubscriptionFlow.tsx pass currentVerificationMethod which is of the wider VerificationMethod type. This can cause TypeScript compilation errors due to type widening. Typing the parameter as string and checking method !== 'iapkit-localhost' avoids this issue.
| export function resolveIapkitVerificationBaseUrl( | |
| method: 'iapkit-localhost' | 'iapkit', | |
| configuredBaseUrl?: string | null, | |
| ): string | undefined { | |
| if (method === 'iapkit') { | |
| return undefined; | |
| } | |
| const baseUrl = configuredBaseUrl?.trim(); | |
| if (!baseUrl) { | |
| throw new Error( | |
| 'IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | |
| ); | |
| } | |
| return baseUrl; | |
| } | |
| export function resolveIapkitVerificationBaseUrl( | |
| method: string, | |
| configuredBaseUrl?: string | null, | |
| ): string | undefined { | |
| if (method !== 'iapkit-localhost') { | |
| return undefined; | |
| } | |
| const baseUrl = configuredBaseUrl?.trim(); | |
| if (!baseUrl) { | |
| throw new Error( | |
| 'IAPKIT_BASE_URL not configured for Local (IAPKit) verification', | |
| ); | |
| } | |
| return baseUrl; | |
| } |
There was a problem hiding this comment.
Not changing this: the call occurs only in the else branch after ignore and local have both been excluded, so TypeScript narrows currentVerificationMethod to iapkit-localhost | iapkit. The strict React Native typecheck passes and includes both flows. Widening the helper to string would hide invalid calls and weaken its IAPKit-only contract.
There was a problem hiding this comment.
Code Review
This pull request implements support for local and self-hosted IAPKit receipt verification under OpenIAP Spec 2.3.1. It introduces a baseUrl property to RequestVerifyPurchaseWithIapkitProps across all supported platforms and frameworks (including Apple, Google, React Native, Expo, Flutter, Godot, KMP, and MAUI) to route verification requests to a custom local or self-hosted server. Additionally, it updates the React Native and Expo examples with a new 'Local (IAPKit)' verification option, implements strict origin validation, and adds E2E testing documentation. Feedback on the changes suggests replacing non-POSIX bash extensions ([[) with standard [ tests in the smoke-server.sh script to ensure portability on POSIX-compliant shells.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| sleep 0.25 | ||
| done | ||
|
|
||
| if [[ "$ready" -ne 1 ]]; then |
There was a problem hiding this comment.
Using [[ is a bash extension and is not supported by POSIX-compliant shells like dash (which is the default /bin/sh on Debian/Ubuntu systems). If this script is executed with /bin/sh, it will result in a syntax error. Consider using the standard [ command instead for better portability.
| if [[ "$ready" -ne 1 ]]; then | |
| if [ "$ready" -ne 1 ]; then |
There was a problem hiding this comment.
Not changing this: smoke-server.sh is an explicit Bash executable (#!/usr/bin/env bash), all repository call sites execute it directly, and the script already depends on BASH_SOURCE, pipefail, local, and existing [[ expressions. Replacing one comparison would not make it POSIX sh compatible; bash -n passes.
| -H "Content-Type: application/json" \ | ||
| --data '{}' \ | ||
| "http://localhost:${PORT}${path}")" | ||
| if [[ "$code" != "$expected" ]]; then |
There was a problem hiding this comment.
Not changing this: smoke-server.sh is an explicit Bash executable (#!/usr/bin/env bash), all repository call sites execute it directly, and the script already depends on Bash-only features. Replacing this one [[ expression would not provide POSIX sh compatibility; bash -n passes.
There was a problem hiding this comment.
Code Review
This pull request introduces support for local and self-hosted IAPKit receipt verification (OpenIAP Spec 2.3.1) by adding a baseUrl parameter to RequestVerifyPurchaseWithIapkitProps across multiple platforms and frameworks, including Apple, Google, React Native, Expo, Flutter, Godot, KMP, and MAUI. It also adds a new E2E test guide for local IAPKit verification with the Martie example, updates documentation, and adds verification tests. The review feedback correctly identifies that the adb reverse command used in several scripts and documentation files does not support the --no-rebind option, which will cause execution errors and should be removed.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| elif adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100; then | ||
| IAPKIT_REVERSE_CREATED=1 | ||
| else | ||
| IAPKIT_REVERSE_CREATED=0 | ||
| IAPKIT_REVERSE_BLOCKED=1 | ||
| echo 'BLOCKED: could not create tcp:3100 reverse mapping without rebinding' >&2 |
There was a problem hiding this comment.
The adb reverse command does not support the --no-rebind option (unlike adb forward). Running this command with --no-rebind will result in an error: unknown port specification '--no-rebind' and cause the script to fail. Since the script already checks if the port is reversed, you can safely run adb reverse without --no-rebind.
| elif adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100; then | |
| IAPKIT_REVERSE_CREATED=1 | |
| else | |
| IAPKIT_REVERSE_CREATED=0 | |
| IAPKIT_REVERSE_BLOCKED=1 | |
| echo 'BLOCKED: could not create tcp:3100 reverse mapping without rebinding' >&2 | |
| elif adb -s "$ANDROID_SERIAL" reverse tcp:3100 tcp:3100; then | |
| IAPKIT_REVERSE_CREATED=1 | |
| else | |
| IAPKIT_REVERSE_CREATED=0 | |
| IAPKIT_REVERSE_BLOCKED=1 | |
| echo 'BLOCKED: could not create tcp:3100 reverse mapping' >&2 |
There was a problem hiding this comment.
Not changing this: Android Platform Tools adb 35.0.2 documents reverse [--no-rebind] REMOTE LOCAL in adb help. The flag is intentional because a mapping can appear after the initial list check; plain reverse could overwrite it. This block instead fails closed when the non-rebinding create cannot proceed.
| elif adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100; then | ||
| IAPKIT_REVERSE_CREATED=1 | ||
| else | ||
| IAPKIT_REVERSE_CREATED=0 | ||
| IAPKIT_REVERSE_BLOCKED=1 | ||
| echo 'BLOCKED: could not create tcp:3100 reverse mapping without rebinding' >&2 |
There was a problem hiding this comment.
The adb reverse command does not support the --no-rebind option. Running this command with --no-rebind will result in an error: unknown port specification '--no-rebind' and cause the script to fail. Since the script already checks if the port is reversed, you can safely run adb reverse without --no-rebind.
| elif adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100; then | |
| IAPKIT_REVERSE_CREATED=1 | |
| else | |
| IAPKIT_REVERSE_CREATED=0 | |
| IAPKIT_REVERSE_BLOCKED=1 | |
| echo 'BLOCKED: could not create tcp:3100 reverse mapping without rebinding' >&2 | |
| elif adb -s "$ANDROID_SERIAL" reverse tcp:3100 tcp:3100; then | |
| IAPKIT_REVERSE_CREATED=1 | |
| else | |
| IAPKIT_REVERSE_CREATED=0 | |
| IAPKIT_REVERSE_BLOCKED=1 | |
| echo 'BLOCKED: could not create tcp:3100 reverse mapping' >&2 |
There was a problem hiding this comment.
Not changing this: Android Platform Tools adb 35.0.2 documents reverse [--no-rebind] REMOTE LOCAL in adb help. The flag preserves the runbook's ownership rule across the list/bind race, and the block explicitly stops as BLOCKED if creation fails.
| cp .env.example .env | ||
| ``` | ||
|
|
||
| For hosted IAPKit, get a key from the [IAPKit dashboard](https://kit.openiap.dev). Set `EXPO_PUBLIC_IAPKIT_API_KEY` to a key issued by the IAPKit/Convex deployment that the selected server uses. For **Local (IAPKit)**, the key and local server must target the same Convex deployment. Also set `EXPO_PUBLIC_IAPKIT_BASE_URL` to the device-reachable HTTP(S) origin only; do not append `/v1/purchase/verify`. A physical iPhone must use the Mac's LAN address. An Android device connected over USB can use `http://127.0.0.1:3100`: inspect `adb -s "$ANDROID_SERIAL" reverse --list`, reuse an exact `tcp:3100` mapping when present, or create it with `adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100`. Record whether this run created the rule and remove only that rule during cleanup; if `--no-rebind` fails, use another port instead of overwriting an existing mapping. |
There was a problem hiding this comment.
Not changing this: Android Platform Tools adb 35.0.2 explicitly supports reverse [--no-rebind] REMOTE LOCAL. Keeping --no-rebind prevents the example instructions from overwriting a mapping that appears between inspection and creation.
| cp example/.env.example example/.env | ||
| ``` | ||
|
|
||
| For hosted IAPKit, get a key from the [IAPKit dashboard](https://kit.openiap.dev). Set `IAPKIT_API_KEY` to a key issued by the IAPKit/Convex deployment that the selected server uses. For **Local (IAPKit)**, the key and local server must target the same Convex deployment. Also set `IAPKIT_BASE_URL` to the device-reachable HTTP(S) origin only; do not append `/v1/purchase/verify`. A physical iPhone must use the Mac's LAN address. An Android device connected over USB can use `http://127.0.0.1:3100`: inspect `adb -s "$ANDROID_SERIAL" reverse --list`, reuse an exact `tcp:3100` mapping when present, or create it with `adb -s "$ANDROID_SERIAL" reverse --no-rebind tcp:3100 tcp:3100`. Record whether this run created the rule and remove only that rule during cleanup; if `--no-rebind` fails, use another port instead of overwriting an existing mapping. |
There was a problem hiding this comment.
Not changing this: Android Platform Tools adb 35.0.2 explicitly supports reverse [--no-rebind] REMOTE LOCAL. Keeping --no-rebind prevents the example instructions from overwriting a mapping that appears between inspection and creation.
| /// <summary>Available in OpenIAP Spec 2.3.1 / openiap-apple 2.4.0 / openiap-google 2.4.0.</summary> | ||
| /// <summary>Base URL for the IAPKit server. Defaults to https://kit.openiap.dev.</summary> | ||
| /// <summary>Set this to a reachable HTTP(S) origin when self-hosting or testing a local IAPKit server.</summary> | ||
| /// <summary>The apiKey must be issued by the same IAPKit/Convex deployment as this server.</summary> | ||
| [JsonPropertyName("baseUrl")] | ||
| public string? BaseUrl { get; init; } |
There was a problem hiding this comment.
Not changing this generated file: the C# generator intentionally emits one inline summary element per GraphQL description line, and the same established pattern appears throughout Types.cs. The MAUI net9/net10 builds run with GenerateDocumentationFile and TreatWarningsAsErrors and pass without XML-doc warnings. A repository-wide generator formatting redesign is separate from this API addition, and generated copies must not be edited directly.
There was a problem hiding this comment.
Code Review
This pull request implements support for local and self-hosted IAPKit receipt verification across the entire OpenIAP ecosystem, updating the OpenIAP Spec to version 2.3.1. It introduces an optional baseUrl parameter to RequestVerifyPurchaseWithIapkitProps, allowing clients to route verification requests to a custom, device-reachable local server instead of the default hosted service. The changes span shared GQL schemas, native packages (openiap-apple, openiap-google), and all framework libraries (react-native-iap, expo-iap, flutter_inapp_purchase, godot-iap, kmp-iap, and OpenIap.Maui). Robust HTTP(S) origin validation has been added to prevent malformed URLs, and the React Native and Expo example apps have been updated with a new "Local (IAPKit)" verification option and comprehensive test suites. Additionally, E2E testing instructions and a new Claude skill have been added to guide developers through physical-device testing. There are no review comments to evaluate, and the implementation is clean and well-tested.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request introduces support for local and self-hosted IAPKit receipt verification across the OpenIAP specification (bumping it to version 2.3.1) and all supported framework libraries (React Native, Expo, Flutter, Godot, KMP, and .NET MAUI) as well as the native Apple and Google packages. It adds a new baseUrl property to the IAPKit verification properties, allowing clients to route verification requests to a custom local or self-hosted server origin instead of defaulting to the hosted service. Comprehensive validation of the custom base URL is implemented across platforms to ensure it is a valid HTTP(S) origin, with extensive unit and integration tests added. Additionally, the Martie React Native and Expo examples have been updated with a new Local (IAPKit) verification option and a custom modal selector, and the local server smoke tests have been hardened to verify port ownership and route handling. No review comments were provided for this pull request.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Summary
iapkit.baseUrlsupport across OpenIAP native packages and framework bridges, with HTTP(S)-origin validation and same-deployment API-key guidance.Local (IAPKit)to the React Native and Expo Martie examples in the exact orderLocal (Device)→Local (IAPKit)→IAPKit→None (Skip).Root cause
IAPKit receipt verification was fixed to the hosted endpoint in native and Vega paths, so a device purchase could not be routed through the locally compiled IAPKit server. The examples also had no distinct local-server choice, and the server smoke probe could accidentally succeed against an older process already owning the port.
Validation
Live receipt vertical
dev.hyo.martie, Apple sandbox.dev.hyo.martie.10bulbs.packages/kitserver backed by the real Martie Dev Convex deployment.891e06ad-01d0-419e-8f2a-c77b156ae037.isValid: true/ready-to-consume; the app finished the transaction.k9751qztk3dp92y9pq3vks3dcx8ae84s.Device UI preview
Local (IAPKit)updates the purchase screen.Automated and build checks
POST /v1/purchase/verify → 400.bun audit:parity,bun audit:docs,bun audit:release-state, docs typecheck/format, andgit diff --check.Release gate
After merge, release the Apple package and required Google flavors first. Then sync the new native version pins and rebuild downstream artifacts before releasing React Native, Expo, Flutter, KMP, or Godot. Monorepo tests use local-project substitution; publishing downstream SDKs against the current Apple/Google pins would otherwise discard
baseUrl.Summary by CodeRabbit