Skip to content

fix: preserve native purchase payloads across SDKs - #252

Merged
hyochan merged 16 commits into
mainfrom
fix/sdk-preserve-native-payloads
Jul 24, 2026
Merged

hyochan merged 16 commits into
mainfrom
fix/sdk-preserve-native-payloads

Conversation

@hyochan

@hyochan hyochan commented Jul 24, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Preserve complete native purchase, renewal, subscription, and verification payloads across openiap-google, React Native, Expo, Flutter, KMP, and MAUI.
  • Keep store-specific semantics intact, including orderless Google Play purchases, Amazon/Vega deferred plan changes, and fail-closed Expo Onside routing.
  • Add generated-contract parity guards, cross-SDK regression coverage, planned release notes, and AI guidance to prevent future payload drift.

Stack normalization

PR #251 has merged into main. This branch was rebased from the reviewed #251 head onto its squash merge commit with an identical resulting Git tree. Files changed now contains only this follow-up payload-preservation work.

Changes

  • openiap-google: reject Amazon receipts when either cancellation signal is present and preserve current/deferred subscription plan metadata.
  • react-native-iap: carry explicit transaction identity through Nitro, avoid synthesizing order IDs for orderless Play purchases, and preserve Apple renewal and Vega plan metadata.
  • expo-iap: fail closed for unavailable Onside operations and preserve Onside subscription, transaction, and Vega deferred-plan metadata.
  • flutter_inapp_purchase: preserve generated purchase and provider-verification payloads, support Horizon verification, recursively normalize bridge maps, and retain typed platform errors.
  • kmp-iap: preserve complete generated Android/iOS purchase fields and recursively normalize Apple bridge dictionaries and nulls.
  • OpenIap.Maui: surface listener schema drift with credential-safe diagnostics and generated payload round-trip coverage.
  • Godot: add generated payload round-trip regression coverage; no runtime or release change.
  • Guardrails: derive expected fields from generated SSOT and structurally audit canonical-first mappings, alternative-store semantics, and cross-wrapper round trips. The tokenizer fault matrix covers TypeScript regex/template syntax, Kotlin escaped identifiers/interpolation/comments, Dart raw/triple/interpolated strings, Unicode indices, and non-LF line separators.

Version and release scope

  • This PR does not bump package metadata and does not publish or deploy anything.
  • OpenIAP Spec remains 2.4.2, the minimum of openiap-apple 2.4.2 and openiap-google 2.5.0.
  • Planned release notes cover openiap-google 2.5.1, react-native-iap 15.6.1, expo-iap 4.7.1, flutter_inapp_purchase 9.6.1, KMP 2.7.1, and OpenIap.Maui 1.4.1.
  • Godot receives test-only coverage and does not require a package release.

Validation

  • Expo: 380 source tests, 85 plugin tests, TypeScript, ESLint
  • React Native: 428 tests, typecheck, ESLint, Nitro specs, Android Kotlin compile
  • Flutter: analyze, 342 tests, example Android APK build
  • Google: Play, Amazon, and Horizon tests/builds
  • KMP: Play, Horizon, Amazon, and iOS tests/builds
  • Godot: 233 tests
  • MAUI: 102 tests and Android binding/application builds
  • Payload audit fault matrix: 28 tests
  • Docs format, typecheck, and production build
  • bun audit:parity, bun audit:docs, bun audit:release-state
  • git diff --check
  • Independent final review: no actionable blocker or harmful redundancy

Preview

No visual recording is applicable. This change affects native payload transport, event mapping, and executable audit coverage without changing a user-facing screen. The automated tests, platform builds, and parity fault matrix are the relevant proof.

Related

Summary by CodeRabbit

  • New Features
    • Expanded purchase payload integrity across Expo, Flutter, React Native, KMP, MAUI, Godot, and native SDK integrations, including transaction identifiers, subscription/renewal metadata, deferred subscription changes, pending products, and richer purchase fields.
    • Added/strengthened Horizon receipt verification payload support.
  • Bug Fixes
    • Improved handling of canceled, restored, deferred, blank/unknown types, malformed payloads, and sensitive data redaction in diagnostics.
    • Prevented unsupported calls from silently falling back and improved listener/event payload deserialization behavior.
  • Documentation
    • Updated guidance on canonical payload preservation and parity validation for native/framework bridges.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 22da3826-3d72-4c79-ab2e-6689e3a27243

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR standardizes generated purchase payload preservation across SDK bridges, adds parity auditing, updates platform serialization and normalization, changes deferred subscription handling, preserves transaction identifiers, and expands regression coverage across multiple integrations.

Changes

Cross-SDK payload integrity

Layer / File(s) Summary
Payload contracts and parity auditing
knowledge/..., scripts/audit-*-parity.*, .github/workflows/ci.yml, packages/gql/...
Documents canonical payload preservation and adds automated cross-language mapping, workflow, parser, and regression checks.
Platform payload mappings and runtime flows
libraries/expo-iap/..., libraries/flutter_inapp_purchase/..., libraries/kmp-iap/..., packages/google/..., libraries/maui-iap/..., libraries/react-native-iap/...
Adds subscription-aware serialization, fail-closed routing, generated verification serialization, normalized decoding, billing metadata preservation, transaction identifiers, deferred-plan handling, cancellation filtering, and listener deserialization.
Regression fixtures and release documentation
libraries/.../test*, packages/google/.../test*, packages/docs/..., libraries/godot-iap/...
Expands fixtures and tests for payload round trips, nested normalization, verification responses, deferred subscriptions, renewal metadata, redaction, and planned cross-SDK releases.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: 🐛 bug, 💨 ci

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.62% 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 clearly matches the PR’s main goal of preserving native purchase payloads across multiple SDKs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sdk-preserve-native-payloads

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.

@hyochan hyochan added cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter_inapp_purchase flutter_inapp_purchase library godot-iap godot-iap library kmp-iap kmp-iap library maui react-native-iap react-native-iap library ⌚️ regression Things that worked previously 📖 documentation Improvements or additions to documentation 📱 iOS Related to iOS 🛠 bugfix All kinds of bug fixes 🤖 android Related to android 🧪 test Issue or pr related to testing labels Jul 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
libraries/react-native-iap/src/vega-adapter.ts (1)

1029-1053: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

includeSuspended option is now a dead no-op.

getAvailablePurchases's filter (Lines 1033-1037) only excludes cancelled receipts and never reads options?.android?.includeSuspended, yet restorePurchases (Line 1546) still passes {android: {includeSuspended: false}}. Since mapReceipt now always sets isSuspendedAndroid: false, this option currently has no effect anywhere — it silently misleads future readers into believing suspended purchases are excluded from restore.

Either drop the stale option from the restorePurchases call, or restore real suspended-state filtering if that semantic is still needed.

🧹 Proposed cleanup
     async restorePurchases(): Promise<void> {
-      const purchases = await getAvailablePurchases({
-        android: {includeSuspended: false},
-      });
+      const purchases = await getAvailablePurchases();
       purchases.forEach(emitPurchaseUpdated);
     },

Also applies to: 1544-1549

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/react-native-iap/src/vega-adapter.ts` around lines 1029 - 1053,
Remove the stale android.includeSuspended option from the restorePurchases call
and its getAvailablePurchases invocation, since getAvailablePurchases does not
implement suspended-purchase filtering and mapReceipt always sets
isSuspendedAndroid to false. Preserve the existing purchase restoration behavior
and type filtering.
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt (1)

1222-1229: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore graceful fallback for malformed iOS purchase payloads.

decodePurchasePayloadIOS catches parsing failures and returns null, so malformed purchase updates can disappear from purchaseUpdatedListener and related consumers. Add legacy alias/default recovery inside normalizePurchasePayloadIOS, and/or wrap PurchaseIOS.fromJson in runCatching with a fallback construction path before relying on strict generated fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`
around lines 1222 - 1229, Update convertAnyToPurchaseIOS and the underlying
normalizePurchasePayloadIOS flow to preserve malformed iOS purchase updates
instead of returning null. Add the legacy alias/default recovery in
normalizePurchasePayloadIOS and wrap PurchaseIOS.fromJson with runCatching,
constructing a fallback PurchaseIOS before accessing strict generated fields.
🧹 Nitpick comments (2)
packages/docs/src/pages/docs/updates/releases.tsx (1)

184-197: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale id/key still says "flutter-purchase-payload-fix" after the cross-SDK reword.

The object id (line 186), key/AnchorLink id (lines 190, 194) still reference the old Flutter-only note, even though the heading and body were reworded to "Cross-SDK native payload integrity patches." Since this note is still "(planned)" and unreleased, renaming to something like cross-sdk-payload-integrity-planned-2026-07-24 would avoid a misleading permalink without breaking any published anchor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/docs/src/pages/docs/updates/releases.tsx` around lines 184 - 197,
Rename the release note identifier consistently from the Flutter-specific value
to a Cross-SDK payload-integrity identifier, such as
cross-sdk-payload-integrity-planned-2026-07-24, in the object id, rendered div
key, and AnchorLink id. Keep the planned date and heading unchanged.
libraries/expo-iap/ios/onside/OnsideIapModule.swift (1)

586-607: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Suffix the new Onside-only helpers with IOS.

Rename these helpers to formatPriceIOS and subscriptionPeriodComponentsIOS, then update their call sites.

As per coding guidelines, “iOS functions must end with IOS.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift` around lines 586 - 607,
Rename the Onside-only helpers formatPrice and subscriptionPeriodComponents to
formatPriceIOS and subscriptionPeriodComponentsIOS, respectively, and update
every call site to use the new names.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 483-490: The introductory offer mapping in the product dictionary
currently collapses all non-zero prices to “empty.” Update the
introductoryPricePaymentModeIOS assignment in the introductoryPrice block to
preserve the provider/SDK payment mode when available, using “empty” only as an
explicit fallback while retaining “free-trial” for zero-priced offers; add
regression coverage for paid pay-as-you-go and pay-up-front offers.

In
`@libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt`:
- Around line 20-21: Update the raw billing payload in
BillingPurchasePayloadMappingTest to use the string state "purchased" for
purchaseState, ensuring the roundtrip matches the mapper’s accepted billing
states and getActiveSubscriptionsHandler’s PURCHASED behavior.

---

Outside diff comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1222-1229: Update convertAnyToPurchaseIOS and the underlying
normalizePurchasePayloadIOS flow to preserve malformed iOS purchase updates
instead of returning null. Add the legacy alias/default recovery in
normalizePurchasePayloadIOS and wrap PurchaseIOS.fromJson with runCatching,
constructing a fallback PurchaseIOS before accessing strict generated fields.

In `@libraries/react-native-iap/src/vega-adapter.ts`:
- Around line 1029-1053: Remove the stale android.includeSuspended option from
the restorePurchases call and its getAvailablePurchases invocation, since
getAvailablePurchases does not implement suspended-purchase filtering and
mapReceipt always sets isSuspendedAndroid to false. Preserve the existing
purchase restoration behavior and type filtering.

---

Nitpick comments:
In `@libraries/expo-iap/ios/onside/OnsideIapModule.swift`:
- Around line 586-607: Rename the Onside-only helpers formatPrice and
subscriptionPeriodComponents to formatPriceIOS and
subscriptionPeriodComponentsIOS, respectively, and update every call site to use
the new names.

In `@packages/docs/src/pages/docs/updates/releases.tsx`:
- Around line 184-197: Rename the release note identifier consistently from the
Flutter-specific value to a Cross-SDK payload-integrity identifier, such as
cross-sdk-payload-integrity-planned-2026-07-24, in the object id, rendered div
key, and AnchorLink id. Keep the planned date and heading unchanged.
🪄 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 Plus

Run ID: 82ac1d09-238f-41d2-8b8b-30342c41f7bc

📥 Commits

Reviewing files that changed from the base of the PR and between 346ee5b and 425d19f.

📒 Files selected for processing (47)
  • knowledge/_claude-context/context.md
  • knowledge/internal/04-platform-packages.md
  • libraries/expo-iap/ios/ExpoIapHelper.swift
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/expo-iap/src/ExpoIapModule.ts
  • libraries/expo-iap/src/__tests__/ExpoIapModule.test.ts
  • libraries/expo-iap/src/__tests__/native-log-redaction.test.js
  • libraries/expo-iap/src/__tests__/vega-adapter.test.ts
  • libraries/expo-iap/src/vega-adapter.ts
  • libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt
  • libraries/flutter_inapp_purchase/ios/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/lib/errors.dart
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/macos/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/test/errors_unit_test.dart
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_active_subscriptions_test.dart
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/flutter_inapp_purchase/test/iapkit_base_url_bridge_test.dart
  • libraries/flutter_inapp_purchase/test/ios_methods_test.dart
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/kmp-iap/library/build.gradle.kts
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt
  • libraries/maui-iap/src/OpenIap.Maui/Platforms/Android/OpenIapAndroid.cs
  • libraries/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/ios/RnIapHelper.swift
  • libraries/react-native-iap/src/__tests__/index.test.ts
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/__tests__/vega-adapter.test.ts
  • libraries/react-native-iap/src/index.ts
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • libraries/react-native-iap/src/utils/type-bridge.ts
  • libraries/react-native-iap/src/vega-adapter.ts
  • packages/docs/src/pages/docs/updates/releases.tsx
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonSubscriptionGroupMappingTest.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/utils/BillingPurchasePayloadMappingTest.kt
  • scripts/audit-non-godot-parity.mjs
  • scripts/audit-purchase-payload-parity.mjs
  • scripts/audit-purchase-payload-parity.test.mjs

Comment thread libraries/expo-iap/ios/onside/OnsideIapModule.swift
@hyochan

hyochan commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Addressed the remaining review-body findings in fffeb10: malformed optional KMP iOS metadata can no longer suppress a valid purchase update, legacy iOS identity aliases are recovered with core-identity guards, Vega restore no longer passes an unsupported suspension option and has deferred-plan regression coverage, the planned release anchor is cross-SDK, and the Onside helpers follow the IOS naming rule. The Android raw purchase-state fixture remains numeric for the BillingClient reason documented in the inline reply.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt (1)

56-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Apply the required IOS suffix to the new test functions.

These iOS-specific Kotlin functions currently end with descriptive text instead of IOS. Rename them to identifiers ending in IOS to follow the repository naming rule.

As per coding guidelines, iOS functions in *.kt files must end with IOS.

Also applies to: 101-102, 125-126, 137-138, 157-158, 185-186, 203-204

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt`
around lines 56 - 57, Rename the iOS-specific test functions in
ProductPayloadNormalizerTestIOS, including preserves canonical purchase metadata
before generated decoding and the other referenced tests, so each identifier
ends with the required IOS suffix. Preserve their test behavior and annotations.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt`:
- Around line 56-57: Rename the iOS-specific test functions in
ProductPayloadNormalizerTestIOS, including preserves canonical purchase metadata
before generated decoding and the other referenced tests, so each identifier
ends with the required IOS suffix. Preserve their test behavior and annotations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da5854d2-eceb-47ba-93dd-7364208ad5e3

📥 Commits

Reviewing files that changed from the base of the PR and between 425d19f and fffeb10.

📒 Files selected for processing (8)
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/expo-iap/src/__tests__/native-log-redaction.test.js
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt
  • libraries/react-native-iap/src/__tests__/vega-adapter.test.ts
  • libraries/react-native-iap/src/vega-adapter.ts
  • packages/docs/src/pages/docs/updates/releases.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
  • libraries/expo-iap/src/tests/native-log-redaction.test.js
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt
  • packages/docs/src/pages/docs/updates/releases.tsx
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/react-native-iap/src/vega-adapter.ts
  • libraries/react-native-iap/src/tests/vega-adapter.test.ts

@hyochan

hyochan commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Reviewed the final CodeRabbit IOS-suffix nitpick and am intentionally not renaming these test display names. The naming SSOT scopes the function rule to iOS functions in packages/apple, while the KMP convention applies platform suffixes to public types and fields. These are private Kotlin test display names under iosTest, and neighboring IosConnectionLifecycleTest and IosErrorMappingTest use the same unsuffixed style. The focused ProductPayloadNormalizerTestIOS suite passes, so no code change is appropriate for this suggestion.

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyochan
hyochan force-pushed the fix/sdk-preserve-native-payloads branch from fffeb10 to e4f9a86 Compare July 24, 2026 16:23
Base automatically changed from fix/flutter-preserve-purchase-payloads to main July 24, 2026 16:27
hyochan added 7 commits July 25, 2026 01:28
Fail closed for unavailable Onside APIs and preserve Onside and Vega subscription metadata without routing through the wrong store runtime.
Carry explicit transaction identity and renewal metadata through Nitro while retaining Vega deferred plan updates and orderless Play semantics.
Preserve generated purchase and provider verification results, support Horizon verification, normalize nested bridge maps, and retain typed platform errors.
Keep Google, Amazon, KMP, and MAUI purchase payloads faithful to their native sources, including subscription transitions, transaction identity, and listener diagnostics.
Derive purchase-field expectations from generated SSOT and fail on canonical-order, alternative-store, or cross-wrapper payload drift.
Record the planned SDK patch releases and add generated-payload preservation guidance to the platform and compiled AI documentation.
Link the stacked cross-SDK implementation PR from the planned payload-integrity release note.
@hyochan
hyochan force-pushed the fix/sdk-preserve-native-payloads branch from af8f66f to e8a3c0c Compare July 24, 2026 16:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
libraries/expo-iap/ios/ExpoIapHelper.swift (1)

67-82: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback for unrecognized type diverges from the RN helper.

RnIapHelper.parseProductQueryType returns .inApp for both empty and unrecognized inputs, but ExpoIapHelper.parseProductQueryType returns .all for any non-empty unrecognized value. For cross-SDK parity, match RN and return .inApp in the default case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libraries/expo-iap/ios/ExpoIapHelper.swift` around lines 67 - 82, Update
ExpoIapHelper.parseProductQueryType so its default case returns .inApp for
unrecognized non-empty values, matching RnIapHelper behavior while preserving
the existing handling of recognized values and empty input.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@libraries/expo-iap/ios/ExpoIapHelper.swift`:
- Around line 67-82: Update ExpoIapHelper.parseProductQueryType so its default
case returns .inApp for unrecognized non-empty values, matching RnIapHelper
behavior while preserving the existing handling of recognized values and empty
input.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e1ceb79-84c9-4013-9b7e-e16fc257bfbb

📥 Commits

Reviewing files that changed from the base of the PR and between fffeb10 and be10efb.

📒 Files selected for processing (49)
  • .github/workflows/ci.yml
  • knowledge/_claude-context/context.md
  • knowledge/internal/04-platform-packages.md
  • libraries/expo-iap/ios/ExpoIapHelper.swift
  • libraries/expo-iap/ios/onside/OnsideIapModule.swift
  • libraries/expo-iap/src/ExpoIapModule.ts
  • libraries/expo-iap/src/__tests__/ExpoIapModule.test.ts
  • libraries/expo-iap/src/__tests__/native-log-redaction.test.js
  • libraries/expo-iap/src/__tests__/vega-adapter.test.ts
  • libraries/expo-iap/src/vega-adapter.ts
  • libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt
  • libraries/flutter_inapp_purchase/ios/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/lib/errors.dart
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/macos/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift
  • libraries/flutter_inapp_purchase/test/errors_unit_test.dart
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_active_subscriptions_test.dart
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/flutter_inapp_purchase/test/iapkit_base_url_bridge_test.dart
  • libraries/flutter_inapp_purchase/test/ios_methods_test.dart
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/kmp-iap/library/build.gradle.kts
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt
  • libraries/maui-iap/src/OpenIap.Maui/Platforms/Android/OpenIapAndroid.cs
  • libraries/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/ios/RnIapHelper.swift
  • libraries/react-native-iap/src/__tests__/index.test.ts
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/__tests__/vega-adapter.test.ts
  • libraries/react-native-iap/src/index.ts
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • libraries/react-native-iap/src/utils/type-bridge.ts
  • libraries/react-native-iap/src/vega-adapter.ts
  • packages/docs/src/pages/docs/updates/releases.tsx
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonSubscriptionGroupMappingTest.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/utils/BillingPurchasePayloadMappingTest.kt
  • packages/gql/src/generated-sync-manifest.test.mjs
  • scripts/audit-non-godot-parity.mjs
  • scripts/audit-purchase-payload-parity.mjs
  • scripts/audit-purchase-payload-parity.test.mjs
🚧 Files skipped from review as they are similar to previous changes (39)
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/BillingPurchasePayloadMappingTest.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/utils/BillingPurchasePayloadMappingTest.kt
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • libraries/flutter_inapp_purchase/lib/errors.dart
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonSubscriptionGroupMappingTest.kt
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/react-native-iap/src/index.ts
  • knowledge/internal/04-platform-packages.md
  • libraries/flutter_inapp_purchase/macos/flutter_inapp_purchase/Sources/flutter_inapp_purchase/FlutterInappPurchasePlugin.swift
  • libraries/expo-iap/src/tests/native-log-redaction.test.js
  • libraries/flutter_inapp_purchase/test/flutter_inapp_purchase_channel_test.dart
  • libraries/react-native-iap/src/tests/index.test.ts
  • libraries/kmp-iap/library/build.gradle.kts
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/flutter_inapp_purchase/test/errors_unit_test.dart
  • libraries/flutter_inapp_purchase/test/ios_methods_test.dart
  • libraries/expo-iap/src/tests/vega-adapter.test.ts
  • knowledge/_claude-context/context.md
  • libraries/flutter_inapp_purchase/android/src/main/kotlin/io/github/hyochan/flutter_inapp_purchase/AndroidInappPurchasePlugin.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/react-native-iap/src/utils/type-bridge.ts
  • packages/docs/src/pages/docs/updates/releases.tsx
  • libraries/flutter_inapp_purchase/test/iapkit_base_url_bridge_test.dart
  • libraries/expo-iap/src/vega-adapter.ts
  • libraries/flutter_inapp_purchase/lib/flutter_inapp_purchase.dart
  • libraries/expo-iap/src/ExpoIapModule.ts
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs
  • libraries/react-native-iap/src/tests/utils/type-bridge.test.ts
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • libraries/expo-iap/src/tests/ExpoIapModule.test.ts
  • libraries/react-native-iap/src/vega-adapter.ts
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt
  • scripts/audit-non-godot-parity.mjs
  • libraries/react-native-iap/src/tests/vega-adapter.test.ts

@hyochan

hyochan commented Jul 24, 2026 •

Copy link
Copy Markdown
Member Author

Fixed the latest CodeRabbit product-query fallback finding in 53b1ca4. Expo iOS now fails closed to ProductQueryType.inApp for unrecognized non-empty values, matching React Native, with source-contract regression coverage. Focused and full Expo tests, plugin tests, TypeScript checks, lint, formatting, and the repository parity audit pass.

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 24, 2026
@hyochan
hyochan merged commit d71c2b3 into main Jul 24, 2026
23 checks passed
@hyochan
hyochan deleted the fix/sdk-preserve-native-payloads branch July 24, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android 🛠 bugfix All kinds of bug fixes cross-platform Cross-platform (both Android & iOS) 📖 documentation Improvements or additions to documentation expo-iap expo-iap library flutter_inapp_purchase flutter_inapp_purchase library godot-iap godot-iap library 📱 iOS Related to iOS kmp-iap kmp-iap library react-native-iap react-native-iap library ⌚️ regression Things that worked previously 🧪 test Issue or pr related to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant