Skip to content

feat(google): expose amazon userId and marketplace on purchases - #275

Merged
hyochan merged 4 commits into
hyodotdev:mainfrom
josef256:feat/amazon-purchase-userid
Aug 3, 2026
Merged

hyochan merged 4 commits into
hyodotdev:mainfrom
josef256:feat/amazon-purchase-userid

Conversation

@josef256

@josef256 josef256 commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

On the Amazon flavor, PurchaseResponse.getUserData() provides a userId and marketplace, but they are dropped when mapping to PurchaseAndroid. Apps that verify receipts server-side via Amazon RVS need userId + receiptId, so there is currently no supported way to obtain the Amazon userId from a purchase. OpenIAP already documents this exact id on RequestVerifyPurchaseWithIapkitAmazonProps.userId, but only for the built-in IAPKit verification path.

Prior art: react-native-iap exposes these fields

For comparison, react-native-iap attaches the Amazon user data to every purchase it emits, so server-side RVS verification works out of the box. From its Amazon Receipt.toMap(userData):

it.putString("purchaseToken", receiptId)
it.putString("userIdAmazon", userData.userId)
it.putString("userMarketplaceAmazon", userData.marketplace)

openiap receives the same PurchaseResponse.userData but does not forward userId/marketplace, so migrating from react-native-iap to openiap loses the ability to verify Amazon receipts on a backend. This PR restores parity.

Change

  • Schema (type-android.graphql): add nullable userIdAmazon and userMarketplaceAmazon to PurchaseAndroid, documented as Amazon-flavor-only (null on Google Play / Horizon).
  • Amazon mapper (OpenIapModule.kt): cache userId/marketplace from onUserDataResponse (already fetched at init) and populate the new fields in buildAmazonPurchase / toPurchase.
  • Generated bindings: regenerated via bun run generate (Kotlin, Swift, TS, Dart, C#, GDScript, kmp, expo-iap, react-native-iap).

Notes

  • Additive and non-breaking — both fields nullable, default null.
  • Field names mirror react-native-iap (userIdAmazon / userMarketplaceAmazon) for easy migration; happy to adjust to match OpenIAP conventions.

Summary by CodeRabbit

  • New Features

    • Added optional Amazon Appstore user ID and marketplace metadata to Android purchase records.
    • Preserved Amazon purchase details across supported platforms and APIs.
    • Improved Amazon receipt verification using purchaser identity and marketplace information.
  • Documentation

    • Documented the new Amazon-specific purchase fields and their availability.

@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 Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hyochan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78bc046a-e739-4adf-9a87-610a5d59fcb9

📥 Commits

Reviewing files that changed from the base of the PR and between 9cd7afa and 7a04649.

📒 Files selected for processing (3)
  • libraries/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs
  • libraries/react-native-iap/src/utils/type-bridge.ts
  • scripts/audit-purchase-payload-parity.mjs
📝 Walkthrough

Walkthrough

PurchaseAndroid now carries optional Amazon Appstore user ID and marketplace metadata across SDK models, serialization, GraphQL, native bridges, and Amazon receipt mapping.

Changes

Amazon purchase metadata

Layer / File(s) Summary
PurchaseAndroid contract updates
packages/gql/src/type-android.graphql, libraries/*/types.*, packages/apple/Sources/Models/Types.swift, packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
Adds optional userIdAmazon and userMarketplaceAmazon fields across supported models.
PurchaseAndroid serialization
libraries/flutter_inapp_purchase/lib/types.dart, libraries/godot-iap/addons/godot-iap/types.gd, libraries/kmp-iap/.../Types.kt, packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
Reads and writes the Amazon metadata fields during JSON serialization.
Amazon purchase mapping and cached identity
packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt, packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonUserDataMappingTest.kt
Caches accepted Amazon user data and passes response or cached values into receipt-derived purchases. Tests cover supplied and absent values.
Cross-platform mapping and documentation
libraries/expo-iap/src/vega-adapter.ts, libraries/react-native-iap/src/vega-adapter.ts, libraries/react-native-iap/src/specs/RnIap.nitro.ts, libraries/react-native-iap/src/utils/type-bridge.ts, libraries/react-native-iap/android/..., libraries/react-native-iap/ios/..., packages/docs/src/pages/docs/types/purchase.tsx, packages/google/openiap/src/{play,horizon}/..., libraries/kmp-iap/library/src/androidMain/..., libraries/maui-iap/tests/...
Propagates Amazon metadata through adapters and bridges, initializes it as null for non-Amazon purchases, and documents and tests the field behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AmazonUserData
  participant OpenIapModule
  participant AmazonReceipt
  participant PurchaseAndroid
  AmazonUserData->>OpenIapModule: Return user ID and marketplace
  OpenIapModule->>OpenIapModule: Cache accepted identity data
  AmazonReceipt->>OpenIapModule: Convert receipt
  OpenIapModule->>PurchaseAndroid: Build purchase with Amazon metadata
Loading

Possibly related PRs

Suggested labels: 🧪 test

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 describes the main change by exposing Amazon user ID and marketplace fields on purchases.
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

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.

@josef256
josef256 marked this pull request as ready for review August 3, 2026 12:58
@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 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: 1

🤖 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 `@packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt`:
- Around line 1010-1015: The successful callback in the user-data handling flow
must pass lifecycle validation through completeOrCache before updating cached
identity state, so stale callbacks after endConnection or newer requests are
rejected. Clear the cached Amazon identity in endConnection, and replace
separate cachedAmazonUserId/cachedAmazonMarketplace updates with one immutable
snapshot consumed consistently by toPurchase.
🪄 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: b47d8db9-ffb9-4602-bdf1-0770ce000f31

📥 Commits

Reviewing files that changed from the base of the PR and between 8e13e07 and 2eaec1c.

⛔ Files ignored due to path filters (6)
  • packages/gql/src/generated/Types.cs is excluded by !**/generated/**
  • packages/gql/src/generated/Types.kt is excluded by !**/generated/**
  • packages/gql/src/generated/Types.swift is excluded by !**/generated/**
  • packages/gql/src/generated/types.dart is excluded by !**/generated/**
  • packages/gql/src/generated/types.gd is excluded by !**/generated/**
  • packages/gql/src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (10)
  • libraries/expo-iap/src/types.ts
  • libraries/flutter_inapp_purchase/lib/types.dart
  • libraries/godot-iap/addons/godot-iap/types.gd
  • libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt
  • libraries/maui-iap/src/OpenIap.Maui/Types.cs
  • libraries/react-native-iap/src/types.ts
  • packages/apple/Sources/Models/Types.swift
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/src/type-android.graphql

Comment thread packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt Outdated
@hyochan hyochan added cross-platform Cross-platform (both Android & iOS) expo-iap expo-iap library flutter-iap godot-iap godot-iap library kmp-iap kmp-iap library maui-iap .NET MAUI SDK react-native-iap react-native-iap library 🎯 feature New feature 📖 documentation Improvements or additions to documentation 🤖 android Related to android labels Aug 3, 2026
@hyochan

hyochan commented Aug 3, 2026

Copy link
Copy Markdown
Member

Thanks for the well-researched PR — the direction is exactly right, and the schema/codegen work was clean. The remaining gap was our SDK parity requirement: new schema fields must be wired through every SDK surface (that's what the failing Audit SDK Parity / MAUI CI jobs enforce).

Rather than round-tripping, I've pushed a commit on top of yours (e72c63b) that completes the wiring:

  • react-native-iap: NitroPurchase transport fields, HybridRnIap.kt mapping, type-bridge.ts conversion, iOS nil passthrough
  • react-native-iap / expo-iap: Vega (mapReceipt) path now populates both fields from the response/cached user data
  • kmp-iap / Play / Horizon mappers: explicit null enumeration to satisfy the parity audit
  • maui-iap: canonical PurchaseAndroid payload fixture + assertions
  • docs: both fields documented on the Purchase type page
  • tests: AmazonUserDataMappingTest in the Amazon flavor

One behavioral change: purchases now prefer the userData carried on each Purchase(Updates)Response, with the getUserData() cache as fallback (refreshed on every successful response) — this avoids stale user attribution after an account switch and still covers responses that arrive without user data.

Field names stay as you proposed (userIdAmazon / userMarketplaceAmazon); we codified that store-suffix convention in knowledge/internal/01-naming-conventions.md.

Will merge once CI is green.

josef256 and others added 2 commits August 4, 2026 05:12
Amazon getUserData() returns a userId + marketplace that server-side
Amazon RVS verification requires (userId + receiptId), but the amazon
flavor dropped them when mapping a purchase — apps doing their own
receipt verification had no way to obtain the userId. (OpenIAP already
documents this id on RequestVerifyPurchaseWithIapkitAmazonProps.userId,
but only for the built-in IAPKit path.)

Add nullable userIdAmazon and userMarketplaceAmazon to PurchaseAndroid
(populated only on the amazon flavor; null on Google Play and Horizon).
The amazon OpenIapModule caches the values from onUserDataResponse and
sets them in buildAmazonPurchase. Additive and non-breaking; all
platform bindings regenerated via `bun run generate`.
Complete the userIdAmazon/userMarketplaceAmazon schema addition so the
fields flow through every SDK surface instead of stopping at the Amazon
module:

- google/amazon: prefer the userData carried on each
  Purchase(Updates)Response over the getUserData() cache; the cache is
  refreshed by every successful response and only used as fallback
- google/play, google/horizon, kmp-iap: enumerate the new fields
  explicitly (always null off-Amazon) to satisfy the parity audit
- react-native-iap: expose the fields on the NitroPurchase transport,
  map them in HybridRnIap.kt and type-bridge.ts, pass nil on iOS
- react-native-iap, expo-iap: populate the fields in the Vega
  mapReceipt path from the response/cached user data
- maui-iap: cover the fields in the canonical PurchaseAndroid payload
  round-trip fixture
- docs: document both fields on the Purchase type page
- knowledge: codify the store-suffix naming rule the fields introduce
- google/testAmazon: add AmazonUserDataMappingTest

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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

🧹 Nitpick comments (1)
packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonUserDataMappingTest.kt (1)

9-41: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add coverage for Amazon cache fallback and precedence.

These tests verify direct builder assignment and null defaults. They do not exercise the selection logic that prefers per-purchase RVS data and falls back to cached data. Add tests for both branches to prevent stale Amazon identity after an account change.

The PR objective defines this precedence as part of the contract.

🤖 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/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonUserDataMappingTest.kt`
around lines 9 - 41, Add tests around the Amazon purchase user-data selection
logic to cover both precedence branches: assert that per-purchase RVS identity
overrides cached Amazon identity, and that cached identity is used when
per-purchase data is unavailable. Anchor the tests to the existing Amazon
mapping/test helpers and verify account changes do not retain stale cached
identity.
🤖 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/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs`:
- Around line 158-160: Update the PurchaseAndroidJson fixture to keep
userIdAmazon and userMarketplaceAmazon null when store is google, and add a
dedicated Amazon fixture with those metadata values plus a round-trip test if
Amazon coverage is required.

In `@libraries/react-native-iap/src/utils/type-bridge.ts`:
- Around line 543-544: Update the non-iOS purchase mapper around userIdAmazon
and userMarketplaceAmazon to return null for both fields unless
nitroPurchase.store equals STORE_AMAZON; preserve the existing nullable-string
conversion for Amazon purchases.

---

Nitpick comments:
In
`@packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonUserDataMappingTest.kt`:
- Around line 9-41: Add tests around the Amazon purchase user-data selection
logic to cover both precedence branches: assert that per-purchase RVS identity
overrides cached Amazon identity, and that cached identity is used when
per-purchase data is unavailable. Anchor the tests to the existing Amazon
mapping/test helpers and verify account changes do not retain stale cached
identity.
🪄 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: 4f4969fb-7ead-4511-b3d8-f4501c03d3e3

📥 Commits

Reviewing files that changed from the base of the PR and between 2eaec1c and e72c63b.

📒 Files selected for processing (15)
  • knowledge/_claude-context/context.md
  • knowledge/internal/01-naming-conventions.md
  • libraries/expo-iap/src/vega-adapter.ts
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • 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/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/types/purchase.tsx
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/testAmazon/java/dev/hyo/openiap/AmazonUserDataMappingTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt

Comment thread libraries/maui-iap/tests/OpenIap.Maui.Tests/RecordJsonTests.cs
Comment thread libraries/react-native-iap/src/utils/type-bridge.ts Outdated
Address CodeRabbit review on PR hyodotdev#275: a late onUserDataResponse callback
could overwrite the active Amazon identity after endConnection or a
newer request, and the two separate @volatile fields allowed a torn
userId/marketplace pair.

- completeOrCache now reports lifecycle acceptance; onUserDataResponse
  caches user data only for accepted callbacks
- purchase and purchase-updates flows refresh the cache only while their
  issuing generation is still current
- cache is a single immutable UserData snapshot, cleared in
  endConnection alongside the generation bump, so purchase mapping
  always reads one consistent identity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hyochan
hyochan force-pushed the feat/amazon-purchase-userid branch from e72c63b to 9cd7afa Compare August 3, 2026 20:17
- react-native-iap type bridge nulls userIdAmazon/userMarketplaceAmazon
  unless the purchase store is amazon, so Amazon identity metadata can
  never leak onto Google Play or Horizon purchases; the parity audit
  allowlist now permits the store guard on those two fields (matching
  the existing transactionId precedent) while still requiring the
  matching nitro field to be read first
- maui-iap gains a realistic Amazon-store round-trip test covering the
  user data fields; the canonical full-coverage fixture stays synthetic
  because WhenWritingNull drops null members from round trips

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hyochan
hyochan merged commit e55720b into hyodotdev:main Aug 3, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android cross-platform Cross-platform (both Android & iOS) 📖 documentation Improvements or additions to documentation expo-iap expo-iap library 🎯 feature New feature flutter-iap godot-iap godot-iap library kmp-iap kmp-iap library maui-iap .NET MAUI SDK react-native-iap react-native-iap library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants