Skip to content

feat(google): add External Payments (8.3.0+) support for 🇯🇵#57

Merged
hyochan merged 4 commits into
mainfrom
feature/external-payments-830
Dec 27, 2025
Merged

feat(google): add External Payments (8.3.0+) support for 🇯🇵#57
hyochan merged 4 commits into
mainfrom
feature/external-payments-830

Conversation

@hyochan

@hyochan hyochan commented Dec 27, 2025

Copy link
Copy Markdown
Member
  • Add DeveloperProvidedBillingListener for External Payments callbacks
  • Add setDeveloperProvidedBillingListener to OpenIapProtocol and OpenIapStore
  • Update AlternativeBillingScreen example with EXTERNAL_PAYMENTS mode
  • Throw UnsupportedOperationException for unsupported Horizon methods

Reference: https://developer.android.com/google/play/billing/release-notes#8-3-0

Summary by CodeRabbit

  • New Features

    • External Payments program (Android 8.3.0+, Japan-only) with side-by-side Google Play vs. developer payment option, configurable launch modes, listener hooks, token reporting, and purchase/subscription API support for developer billing.
  • Documentation

    • Expanded guides, examples, migration notes, diagrams, and best-practices for External Payments and developer-provided billing (including listener usage and token reporting).
  • Style

    • Reduced navigation vertical spacing and tightened nav link padding.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add DeveloperProvidedBillingListener for External Payments callbacks
- Add setDeveloperProvidedBillingListener to OpenIapProtocol and OpenIapStore
- Update AlternativeBillingScreen example with EXTERNAL_PAYMENTS mode
- Throw UnsupportedOperationException for unsupported Horizon methods
@coderabbitai

coderabbitai Bot commented Dec 27, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds Android External Payments (Google Play Billing 8.3.0+) support: new billing program and launch-mode enums, developer-provided billing event/listener/types, propagation of a developerBillingOption through purchase/subscription requests, Play/Horizon wiring, docs, and example updates (Japan-only external payments).

Changes

Cohort / File(s) Summary
Core models (Swift)
packages/apple/Sources/Models/Types.swift
Added BillingProgramAndroid.externalPayments; new enums/structs for developer billing and developerBillingOption propagation into purchase/subscription props.
GraphQL schema
packages/gql/src/type.graphql, packages/gql/src/type-android.graphql
Added EXTERNAL_PAYMENTS to BillingProgramAndroid; new enums/inputs DeveloperBillingLaunchModeAndroid, DeveloperBillingOptionParamsAndroid, BillingProgramReportingDetailsParamsAndroid; new type DeveloperProvidedBillingDetailsAndroid; added developerBillingOption to Android purchase/subscription inputs; added IapEvent.DeveloperProvidedBillingAndroid.
Android core types & JSON mapping
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
Added ExternalPayments, DeveloperBillingLaunchModeAndroid, DeveloperProvidedBillingDetailsAndroid, BillingProgramReportingDetailsParamsAndroid, DeveloperBillingOptionParamsAndroid; extended fromJson/toJson and threaded developerBillingOption into RequestPurchase/RequestSubscription props.
Listener types
packages/google/openiap/src/main/java/dev/hyo/openiap/listener/*, packages/google/openiap/src/main/java/dev/hyo/openiap/listener/OpenIapListener.kt
New DeveloperProvidedBillingDetails data class and DeveloperProvidedBillingListener; new OpenIapDeveloperProvidedBillingListener functional interface for internal callbacks.
Protocol / Store APIs
packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt, packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
Added set/add/remove developer-provided billing listener methods to expose new listener surface.
Play implementation
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt, packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
Wire ExternalPayments program (8.3.0+); constructor/listener plumbing updated; reflection helpers to enable developerBillingOption in BillingFlowParams; AndroidPurchaseArgs now carries optional developerBillingOption.
Horizon implementation
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
Added listener collections and new listener API stubs that throw UnsupportedOperationException on Horizon to indicate non-support.
Example app
packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
Added BillingModeOption.EXTERNAL_PAYMENTS, UI and flow to demonstrate external payments, and registers user-choice & developer-provided billing listeners.
Docs — External Payments & Types
packages/docs/src/pages/docs/features/external-purchase.tsx, packages/docs/src/pages/docs/types/alternative.tsx, packages/docs/src/pages/docs/events.tsx
Large documentation additions: External Payments (8.3.0+, Japan-only), developer-provided billing event docs, multi-language examples, flow diagrams, migration notes, token reporting guidance; events page duplicated in diff.
Docs — Updates feed & UI spacing
packages/docs/src/pages/docs/updates/notes.tsx, packages/docs/src/components/MenuDropdown.tsx, packages/docs/src/styles/documentation.css
Added release note for External Payments 8.3.0; reduced nav vertical spacing and adjusted MenuDropdown padding.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant OpenIapModule
    participant GooglePlay as GooglePlayBilling
    participant DevListener as DeveloperListener
    participant Backend

    User->>App: Tap "Buy (External Payments)"
    App->>OpenIapModule: launchBillingFlow(developerBillingOption)
    OpenIapModule->>GooglePlay: start billing flow (includes DeveloperBillingOption)

    alt User selects developer billing
        GooglePlay->>OpenIapModule: onUserSelectedDeveloperBilling(token)
        OpenIapModule->>App: emit IapEvent.DeveloperProvidedBillingAndroid(details)
        OpenIapModule->>DevListener: onDeveloperProvidedBilling(details)
        DevListener->>Backend: report externalTransactionToken (within 24h)
        Backend->>GooglePlay: report token usage
        DevListener->>App: confirm success
    else User selects Google Play payment
        GooglePlay->>OpenIapModule: regular purchase callback
        OpenIapModule->>App: deliver purchase result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

📖 documentation

Poem

🐰 I hopped a thread and found a way,

Two pay paths dance in bright array,
Tokens bob like lantern light,
Report them quick to keep things right,
A tiny hop — a safer pay. 🎋

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding External Payments (8.3.0+) support for Japan, which aligns with the extensive modifications across type definitions, listeners, documentation, and example code.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/external-payments-830

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa3664 and f925199.

📒 Files selected for processing (1)
  • packages/docs/src/pages/docs/android-setup.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: iOS-specific functions must end with IOS suffix (e.g., clearTransactionIOS, getAppTransactionIOS)
Android-specific functions must end with Android suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid)
Use action prefixes for function naming: get for retrieval, request for async operations, clear for removal, is/has for boolean checks, show/present for UI display, begin/finish/end for process control

Prefer interface for defining object shapes in TypeScript

Files:

  • packages/docs/src/pages/docs/android-setup.tsx
packages/docs/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{ts,tsx}: Use kebab-case for search modal IDs (e.g., id: 'request-products')
Modal state should be defined once at the app root level using Preact Signals (signal from '@preact/signals-react'), not instantiated multiple times
ALL components must fit within parent boundaries and never overflow outside parent containers; use overflow-hidden, break-words, and whitespace-nowrap as needed
Delete unused components, functions, and imports immediately; do not keep commented-out code or unused variables
ANY function that returns a Promise must be wrapped with void operator when used where a void return is expected (e.g., event handlers)

Files:

  • packages/docs/src/pages/docs/android-setup.tsx
packages/docs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*: Before committing, run npx prettier --write, npm run lint, bun run tsc or npm run typecheck, and npm run build to verify formatting, linting, types, and build success
Use conventional commit format with lowercase type prefix and lowercase description (e.g., feat: add user authentication)

Files:

  • packages/docs/src/pages/docs/android-setup.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable and function names
Use PascalCase for class and component names
Always use async/await for handling promises instead of .then() chains
Add JSDoc comments for public functions and exported APIs
Use const by default, let if reassignment is needed, avoid var

Files:

  • packages/docs/src/pages/docs/android-setup.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap-versions.json : To update OpenIAP GraphQL types in the Android library, edit the `gql` field in `openiap-versions.json`, then run `./scripts/generate-types.sh` and compile to verify

Applied to files:

  • packages/docs/src/pages/docs/android-setup.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helper functions in `openiap/src/main/java/dev/hyo/openiap/utils/`

Applied to files:

  • packages/docs/src/pages/docs/android-setup.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/**/*.kt : DO NOT add `Android` suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use `acknowledgePurchase()` not `acknowledgePurchaseAndroid()`)

Applied to files:

  • packages/docs/src/pages/docs/android-setup.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/apple/openiap-versions.json : To update OpenIAP GraphQL types in the iOS/macOS library, edit the `gql` field in `openiap-versions.json`, run `./scripts/generate-types.sh`, and run `swift test` to verify compatibility

Applied to files:

  • packages/docs/src/pages/docs/android-setup.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : DO NOT edit `openiap/src/main/Types.kt` as it is auto-generated from the GraphQL schema; regenerate it using `./scripts/generate-types.sh`

Applied to files:

  • packages/docs/src/pages/docs/android-setup.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (1)
packages/docs/src/pages/docs/android-setup.tsx (1)

221-241: Maven coordinates verified—documentation uses appropriate versioning pattern.

The Maven coordinates io.github.hyochan.openiap:openiap-google are correct and published on Maven Central (latest version 1.3.16). The ${"$"}version placeholder approach is appropriate for documentation, as it remains valid across version updates without requiring maintenance. The Maven Central link provides users a clear path to find the latest version.

No changes needed.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@hyochan hyochan added 🎯 feature New feature 🤖 android Related to android labels Dec 27, 2025

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt (1)

366-383: Include google props when deriving skuForStatus in requestPurchase

requestPurchase currently derives skuForStatus only from the deprecated .android props:

val skuForStatus = when (val request = props.request) {
    is RequestPurchaseProps.Request.Purchase -> request.value.android?.skus?.firstOrNull()
    is RequestPurchaseProps.Request.Subscription -> request.value.android?.skus?.firstOrNull()
}

Now that newer flows (including the External Payments example) use the recommended google = RequestPurchaseAndroidProps(...) field, skuForStatus will be null for those calls. That means:

  • pendingRequestProductId is never set for those purchases.
  • The purchasing loading state isn’t toggled.
  • Any errors during the flow won’t be associated with the correct product ID (or with any product), which degrades the example UI behavior.

Consider preferring google when present and falling back to android for backward compatibility, e.g.:

Suggested update to derive `skuForStatus`
-    val skuForStatus = when (val request = props.request) {
-        is RequestPurchaseProps.Request.Purchase -> request.value.android?.skus?.firstOrNull()
-        is RequestPurchaseProps.Request.Subscription -> request.value.android?.skus?.firstOrNull()
-    }
+    val skuForStatus = when (val request = props.request) {
+        is RequestPurchaseProps.Request.Purchase -> {
+            val platforms = request.value
+            platforms.google?.skus?.firstOrNull()
+                ?: platforms.android?.skus?.firstOrNull()
+        }
+        is RequestPurchaseProps.Request.Subscription -> {
+            val platforms = request.value
+            platforms.google?.skus?.firstOrNull()
+                ?: platforms.android?.skus?.firstOrNull()
+        }
+    }

This keeps existing behavior for callers still using .android while fixing status tracking for the new google-based flows.

🧹 Nitpick comments (5)
packages/docs/src/pages/docs/events.tsx (1)

38-39: Verify TypeScript API name for Developer Provided Billing listener

The new docs introduce DeveloperProvidedBillingAndroid in IapEvent and a developerProvidedBillingListener(...) helper in the TS examples, while the existing User Choice event exposes userChoiceBillingListenerAndroid(...).

Given the guidelines that Android-specific TS functions should carry an Android suffix and the existing User Choice API already follows that pattern, please double-check:

  • What the actual exported function name is (developerProvidedBillingListener vs developerProvidedBillingListenerAndroid).
  • Whether this listener is Android-only (it appears to be) or intentionally cross-platform.

If it is Android-only and exported as developerProvidedBillingListenerAndroid, the examples here should be updated to use that name for consistency and to avoid confusing consumers. If the API is intentionally named without the suffix, it would be good to bring the rest of the docs and naming guidelines into alignment.

Also applies to: 784-895

packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)

107-109: Remove unused Horizon listener sets or wire them when support is added

On the Horizon module you’ve:

  • Added userChoiceBillingListeners / developerProvidedBillingListeners fields, but
  • All related methods (setUserChoiceBillingListener, setDeveloperProvidedBillingListener, add*/remove*UserChoiceBillingListener, add*/remove*DeveloperProvidedBillingListener) immediately throw UnsupportedOperationException.

Given External Payments and these listener-based APIs are not supported on Meta Horizon, the explicit exceptions are fine and match the PR objective. However, the new mutableSetOf fields are currently never used and will trigger “unused” warnings without adding behavior.

You can either:

  • Remove the unused sets for now (simplest, less noise), or
  • Keep them but add a brief comment that they are placeholders for a future Horizon implementation so readers don’t assume they’re wired up.

The functional behavior (throwing for unsupported features) looks correct; this is just a small cleanup / clarity point.

Also applies to: 1020-1045

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (3)

108-112: Consider thread-safe collections for listener sets.

The listener collections (purchaseUpdateListeners, purchaseErrorListeners, userChoiceBillingListeners, developerProvidedBillingListeners) use mutableSetOf which is not thread-safe. If listeners are added/removed from different threads while callbacks are being dispatched, this could cause ConcurrentModificationException.

🔎 Consider using thread-safe collections
import java.util.Collections

private val purchaseUpdateListeners = Collections.synchronizedSet(mutableSetOf<OpenIapPurchaseUpdateListener>())
private val purchaseErrorListeners = Collections.synchronizedSet(mutableSetOf<OpenIapPurchaseErrorListener>())
private val userChoiceBillingListeners = Collections.synchronizedSet(mutableSetOf<OpenIapUserChoiceBillingListener>())
private val developerProvidedBillingListeners = Collections.synchronizedSet(mutableSetOf<OpenIapDeveloperProvidedBillingListener>())

Alternatively, use CopyOnWriteArraySet if reads are more frequent than writes.


1694-1700: Include exception details in warning logs for better diagnostics.

The NoSuchMethodException and ClassNotFoundException catch blocks log warnings but don't include the exception details, making debugging harder when these occur. The final Exception catch properly includes the exception.

🔎 Proposed fix to include exception details
         } catch (e: NoSuchMethodException) {
-            OpenIapLog.w("DeveloperBillingOption not found. Requires Billing Library 8.3.0+", TAG)
+            OpenIapLog.w("DeveloperBillingOption not found. Requires Billing Library 8.3.0+: ${e.message}", TAG)
         } catch (e: ClassNotFoundException) {
-            OpenIapLog.w("DeveloperBillingOptionParams class not found. Requires Billing Library 8.3.0+", TAG)
+            OpenIapLog.w("DeveloperBillingOptionParams class not found. Requires Billing Library 8.3.0+: ${e.message}", TAG)
         } catch (e: Exception) {
             OpenIapLog.e("Failed to apply DeveloperBillingOption: ${e.message}", e, TAG)
         }

1408-1428: Include exception details in warning logs for billing program initialization.

Similar to the applyDeveloperBillingOption method, the exception catch blocks here don't include the exception details in the log messages. While these are expected when running on older Billing Library versions, including e.message helps with debugging unexpected failures.

🔎 Proposed fix to include exception details
                     } catch (e: NoSuchMethodException) {
-                        OpenIapLog.w("✗ EXTERNAL_PAYMENTS not found. Requires Billing Library 8.3.0+", TAG)
+                        OpenIapLog.w("✗ EXTERNAL_PAYMENTS not found. Requires Billing Library 8.3.0+: ${e.message}", TAG)
                     } catch (e: Exception) {
                         OpenIapLog.w("✗ Failed to enable EXTERNAL_PAYMENTS: ${e.message}", TAG)
                     }
                 } else {
                     // For other programs, use the simpler enableBillingProgram method
                     try {
                         val method = builder.javaClass.getMethod("enableBillingProgram", Int::class.javaPrimitiveType)
                         method.invoke(builder, programConstant)
                         OpenIapLog.d("✓ Billing program enabled: $program (constant=$programConstant)", TAG)
                     } catch (e: NoSuchMethodException) {
-                        OpenIapLog.w("✗ enableBillingProgram not found. Requires Billing Library 8.2.0+", TAG)
+                        OpenIapLog.w("✗ enableBillingProgram not found. Requires Billing Library 8.2.0+: ${e.message}", TAG)
                     } catch (e: Exception) {
                         OpenIapLog.w("✗ Failed to enable billing program $program: ${e.message}", TAG)
                     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e60e324 and 9f433c0.

⛔ Files ignored due to path filters (4)
  • 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.ts is excluded by !**/generated/**
📒 Files selected for processing (18)
  • packages/apple/Sources/Models/Types.swift
  • packages/docs/src/components/MenuDropdown.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/styles/documentation.css
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/DeveloperProvidedBillingListener.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/OpenIapListener.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/gql/src/type-android.graphql
  • packages/gql/src/type.graphql
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: iOS-specific functions must end with IOS suffix (e.g., clearTransactionIOS, getAppTransactionIOS)
Android-specific functions must end with Android suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid)
Use action prefixes for function naming: get for retrieval, request for async operations, clear for removal, is/has for boolean checks, show/present for UI display, begin/finish/end for process control

Prefer interface for defining object shapes in TypeScript

Files:

  • packages/docs/src/components/MenuDropdown.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/docs/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{ts,tsx}: Use kebab-case for search modal IDs (e.g., id: 'request-products')
Modal state should be defined once at the app root level using Preact Signals (signal from '@preact/signals-react'), not instantiated multiple times
ALL components must fit within parent boundaries and never overflow outside parent containers; use overflow-hidden, break-words, and whitespace-nowrap as needed
Delete unused components, functions, and imports immediately; do not keep commented-out code or unused variables
ANY function that returns a Promise must be wrapped with void operator when used where a void return is expected (e.g., event handlers)

Files:

  • packages/docs/src/components/MenuDropdown.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/docs/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable components in src/components/ directory; co-locate single-use components with their parent component. For components with sub-components used only within them, create a folder with index.tsx for the main component

Files:

  • packages/docs/src/components/MenuDropdown.tsx
packages/docs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*: Before committing, run npx prettier --write, npm run lint, bun run tsc or npm run typecheck, and npm run build to verify formatting, linting, types, and build success
Use conventional commit format with lowercase type prefix and lowercase description (e.g., feat: add user authentication)

Files:

  • packages/docs/src/components/MenuDropdown.tsx
  • packages/docs/src/styles/documentation.css
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable and function names
Use PascalCase for class and component names
Always use async/await for handling promises instead of .then() chains
Add JSDoc comments for public functions and exported APIs
Use const by default, let if reassignment is needed, avoid var

Files:

  • packages/docs/src/components/MenuDropdown.tsx
  • packages/docs/src/pages/docs/updates/notes.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/google/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

DO NOT add Android suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use acknowledgePurchase() not acknowledgePurchaseAndroid())

Files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/DeveloperProvidedBillingListener.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/OpenIapListener.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
packages/gql/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Run bun run generate to regenerate types for all platforms (TypeScript, Swift, Kotlin, Dart) from the GraphQL schema

Files:

  • packages/gql/src/type.graphql
  • packages/gql/src/type-android.graphql
packages/apple/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

packages/apple/**/*.swift: iOS-specific functions MUST have IOS suffix (e.g., presentCodeRedemptionSheetIOS(), showManageSubscriptionsIOS())
Use Pascal case for acronyms at the beginning or middle of names (e.g., IapManager, IapPurchase), but ALL CAPS only when appearing as a suffix (e.g., ProductIAP, OpenIAP)

Files:

  • packages/apple/Sources/Models/Types.swift
packages/apple/Sources/Models/Types.swift

📄 CodeRabbit inference engine (CLAUDE.md)

DO NOT edit Types.swift in Sources/Models/ as it is auto-generated from the OpenIAP GraphQL schema; regenerate using ./scripts/generate-types.sh

Files:

  • packages/apple/Sources/Models/Types.swift
packages/apple/Sources/Models/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

OpenIAP official types in Sources/Models/ must match types defined at openiap.dev/docs/types

Files:

  • packages/apple/Sources/Models/Types.swift
🧠 Learnings (9)
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/**/*.kt : DO NOT add `Android` suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use `acknowledgePurchase()` not `acknowledgePurchaseAndroid()`)

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/DeveloperProvidedBillingListener.kt
  • packages/docs/src/pages/docs/events.tsx
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/src/type-android.graphql
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helper functions in `openiap/src/main/java/dev/hyo/openiap/utils/`

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : DO NOT edit `openiap/src/main/Types.kt` as it is auto-generated from the GraphQL schema; regenerate it using `./scripts/generate-types.sh`

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt
  • packages/google/openiap/src/main/java/dev/hyo/openiap/listener/DeveloperProvidedBillingListener.kt
  • packages/gql/src/type.graphql
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/src/type-android.graphql
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap-versions.json : To update OpenIAP GraphQL types in the Android library, edit the `gql` field in `openiap-versions.json`, then run `./scripts/generate-types.sh` and compile to verify

Applied to files:

  • packages/gql/src/type.graphql
  • packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
  • packages/gql/src/type-android.graphql
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/apple/openiap-versions.json : To update OpenIAP GraphQL types in the iOS/macOS library, edit the `gql` field in `openiap-versions.json`, run `./scripts/generate-types.sh`, and run `swift test` to verify compatibility

Applied to files:

  • packages/gql/src/type.graphql
  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/apple/Sources/Models/Types.swift : DO NOT edit `Types.swift` in `Sources/Models/` as it is auto-generated from the OpenIAP GraphQL schema; regenerate using `./scripts/generate-types.sh`

Applied to files:

  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : OpenIAP official types in `Sources/Models/` must match types defined at [openiap.dev/docs/types](https://www.openiap.dev/docs/types)

Applied to files:

  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/apple/Sources/Helpers/**/*.swift : Place internal helper classes in `Sources/Helpers/` (e.g., `ProductManager.swift` for thread-safe caching, `IapStatus.swift` for UI status); these are NOT official OpenIAP types

Applied to files:

  • packages/apple/Sources/Models/Types.swift
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to **/*.{ts,tsx} : Android-specific functions must end with `Android` suffix (e.g., `acknowledgePurchaseAndroid`, `consumePurchaseAndroid`)

Applied to files:

  • packages/docs/src/pages/docs/events.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
🧬 Code graph analysis (3)
packages/docs/src/pages/docs/updates/notes.tsx (2)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2)
  • code (1843-1865)
  • code (2489-2508)
packages/gql/src/generated/Types.kt (2)
  • code (1916-1938)
  • code (2562-2581)
packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt (1)
packages/gql/src/generated/types.ts (4)
  • RequestPurchaseProps (1002-1016)
  • RequestPurchasePropsByPlatforms (1026-1035)
  • RequestPurchaseAndroidProps (965-980)
  • DeveloperBillingOptionParamsAndroid (129-136)
packages/docs/src/pages/docs/features/external-purchase.tsx (2)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2)
  • code (1843-1865)
  • code (2489-2508)
packages/gql/src/generated/Types.kt (2)
  • code (1916-1938)
  • code (2562-2581)
🪛 detekt (1.23.8)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt

[warning] 1412-1412: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1423-1423: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1694-1694: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1696-1696: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🪛 SwiftLint (0.57.0)
packages/apple/Sources/Models/Types.swift

[Warning] 934-934: Type name 'BillingProgramReportingDetailsParamsAndroid' should be between 3 and 40 characters long

(type_name)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Android
  • GitHub Check: Test Android
🔇 Additional comments (30)
packages/docs/src/components/MenuDropdown.tsx (1)

66-66: Consistent styling alignment with CSS updates.

The inline padding 0.4rem 0 matches the updated .docs-nav a padding in documentation.css, maintaining visual consistency across the navigation.

packages/docs/src/styles/documentation.css (1)

57-71: Navigation spacing refinements look good.

The coordinated reduction in margins and padding creates a more compact navigation layout. The changes are consistent across both CSS rules and the inline styles in MenuDropdown.tsx.

packages/google/openiap/src/main/java/dev/hyo/openiap/listener/OpenIapListener.kt (1)

42-53: New listener interface follows existing patterns.

The OpenIapDeveloperProvidedBillingListener correctly uses fun interface for SAM conversion (consistent with other listeners), includes appropriate KDoc documentation, and follows the established naming convention without the Android suffix per coding guidelines.

packages/docs/src/pages/docs/updates/notes.tsx (1)

26-163: Well-structured release note for External Payments feature.

The new note entry provides comprehensive documentation including:

  • New APIs and event types
  • Comparison table distinguishing from User Choice Billing
  • Proper external links with security attributes (rel="noopener noreferrer")
  • Internal documentation cross-references

The structure follows existing patterns and maintains chronological ordering.

packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt (1)

96-108: New developerBillingOption field correctly integrated.

The nullable field type is appropriate since developer billing is optional. The field follows the existing pattern and is consistently propagated in both the Purchase and Subscription branches of toAndroidPurchaseArgs().

packages/docs/src/pages/docs/features/external-purchase.tsx (5)

1260-1277: Comprehensive External Payments section with clear availability scope.

The documentation clearly establishes the Japan-only availability and 8.3.0+ requirement upfront. The info alert appropriately guides developers to check availability before relying on the feature.


1299-1380: TypeScript example is well-structured.

The example demonstrates the complete flow including listener setup, availability check, and token reporting. The comments clearly explain each step and the callback behavior for both Google Play and developer billing paths.


1382-1470: Kotlin example correctly uses library APIs.

The Kotlin example correctly uses addDeveloperProvidedBillingListener (without Android suffix per coding guidelines for the Android-only package) and properly demonstrates RequestPurchaseProps construction with the new developerBillingOption parameter.


1544-1612: Clear flow diagram and critical token reporting warning.

The flow diagram table provides an excellent step-by-step reference. The warning about the 24-hour token reporting requirement is appropriately prominent and mentions the specific API endpoint needed.


62-62: Platform support table updated.

The table now correctly reflects the progression of billing library versions: legacy (6.2+), recommended (8.2.0+), and External Payments (8.3.0+).

packages/google/openiap/src/main/java/dev/hyo/openiap/listener/DeveloperProvidedBillingListener.kt (1)

1-28: Not a duplication — these listeners serve different purposes with distinct architectural roles.

DeveloperProvidedBillingListener is the new public-facing listener interface (set via setDeveloperProvidedBillingListener()), while OpenIapDeveloperProvidedBillingListener is the internal multi-listener collection (managed via add/remove methods in OpenIapModule). They use different data types intentionally: DeveloperProvidedBillingDetails (simpler) vs DeveloperProvidedBillingDetailsAndroid (with Android platform details). This separation is intentional and not a consolidation concern.

packages/gql/src/type.graphql (1)

31-35: New enum value correctly follows naming conventions and types are regenerated.

The DeveloperProvidedBillingAndroid enum value follows the established pattern for platform-specific events in the IapEvent enum (matching PromotedProductIOS and UserChoiceBillingAndroid), includes helpful documentation about availability (8.3.0+), and has been properly regenerated in all type files (packages/gql/src/generated/Types.kt and packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt).

packages/gql/src/type-android.graphql (1)

270-276: External Payments GraphQL schema looks consistent; remember to regenerate types

The schema extensions for External Payments and Developer Provided Billing look well-structured:

  • developerBillingOption added to both RequestPurchaseAndroidProps and RequestSubscriptionAndroidProps as an optional DeveloperBillingOptionParamsAndroid, matching the intended billingProgram/linkUri/launchMode shape.
  • BillingProgramAndroid gains EXTERNAL_PAYMENTS with clear 8.3.0+ / Japan-only documentation.
  • New definitions for DeveloperBillingLaunchModeAndroid, DeveloperBillingOptionParamsAndroid, DeveloperProvidedBillingDetailsAndroid, and BillingProgramReportingDetailsParamsAndroid align with the Android BillingClient API.

Since this is a schema change in packages/gql, please ensure you regenerate and commit the corresponding generated types for all platforms (TS, Swift, Kotlin, Dart), e.g. via your existing tooling (bun run generate or ./scripts/generate-types.sh), and confirm that the generated DeveloperBillingOptionParamsAndroid / DeveloperProvidedBillingDetailsAndroid types match the Kotlin and TS usage introduced in this PR.

Also applies to: 313-318, 517-542, 640-709

packages/google/openiap/src/main/java/dev/hyo/openiap/OpenIapProtocol.kt (1)

4-6: All implementations of OpenIapProtocol have been properly updated with the new Developer Provided Billing methods

Verification confirms all protocol implementors now define the three required methods:

  • Horizon OpenIapModule: Methods implemented with UnsupportedOperationException (appropriate since External Payments is Play-only)
  • Play OpenIapModule: Methods fully implemented with real functionality
  • OpenIapStore: Convenience methods delegate to module implementations

No test fakes or mocks implementing OpenIapProtocol were found, so no additional updates are needed. The API is consistent with the existing User Choice listener pattern.

packages/apple/Sources/Models/Types.swift (3)

1-6: Verify this file was properly regenerated.

Per coding guidelines, Types.swift in Sources/Models/ is auto-generated from the OpenIAP GraphQL schema and should not be edited directly. Please confirm these changes were made by running ./scripts/generate-types.sh after updating the GraphQL schema, rather than manual edits.

Based on learnings, this file should be regenerated using ./scripts/generate-types.sh and not edited directly.


23-53: LGTM - New enums for External Payments.

The BillingProgramAndroid.externalPayments case and DeveloperBillingLaunchModeAndroid enum follow the established patterns. Documentation properly indicates the 8.3.0+ availability requirement.


931-943: LGTM - Request props and structs properly updated.

The new structs (BillingProgramReportingDetailsParamsAndroid, DeveloperBillingOptionParamsAndroid, DeveloperProvidedBillingDetailsAndroid) and the developerBillingOption additions to RequestPurchaseAndroidProps and RequestSubscriptionAndroidProps follow the established patterns. The initializers correctly include the new optional parameters.

Note: The SwiftLint warning about BillingProgramReportingDetailsParamsAndroid exceeding 40 characters is acceptable here since this is auto-generated from the GraphQL schema and the name should match the API.

Also applies to: 960-980, 1102-1128, 1264-1309

packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (4)

1196-1202: LGTM - Listener management methods follow existing patterns.

The addDeveloperProvidedBillingListener and removeDeveloperProvidedBillingListener methods are consistent with the existing listener management pattern used for other listener types.


1553-1633: LGTM - External Payments initialization with proper reflection approach.

The enableExternalPaymentsProgram method correctly uses reflection to support optional Billing Library 8.3.0+ APIs. The proxy-based DeveloperProvidedBillingListener implementation properly extracts the external transaction token and notifies registered listeners.


1482-1488: LGTM - Setter method follows existing patterns.

The setDeveloperProvidedBillingListener method follows the same pattern as setUserChoiceBillingListener.


935-938: LGTM - Developer billing option integration in purchase flow.

The developerBillingOption is properly applied to the BillingFlowParams builder when provided, enabling the side-by-side choice for external payments flow.

packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (9)

1-4: Auto-generated file - verified proper structure.

This file is auto-generated from the GraphQL schema. The External Payments types have been properly generated with consistent patterns for enums, data classes, and JSON serialization methods.

Based on learnings, ensure any future changes are made by updating the GraphQL schema and running ./scripts/generate-types.sh to regenerate this file.


47-88: LGTM! External Payments enum properly added.

The ExternalPayments enum value is well-documented, noting it's Japan-only and requires Google Play Billing Library 8.3.0+. The JSON parsing correctly handles both kebab-case and PascalCase variants, consistent with other enum values.


90-124: LGTM! New launch mode enum properly implemented.

The DeveloperBillingLaunchModeAndroid enum is well-structured with clear documentation for each launch mode. The implementation follows the established pattern for enums in this file.


342-371: LGTM! Developer billing event properly added.

The new DeveloperProvidedBillingAndroid event is well-documented and follows the established pattern for IAP events in this file. The documentation clearly indicates when this event fires and the version requirement.


945-971: LGTM! Billing details class properly implemented.

The DeveloperProvidedBillingDetailsAndroid data class follows the established pattern with proper JSON serialization. The documentation importantly highlights the 24-hour reporting deadline for the transaction token.


2569-2591: LGTM! Reporting params class properly structured.

The BillingProgramReportingDetailsParamsAndroid input class is well-documented and follows the standard pattern for parameter objects. The default to Unspecified in fromJson is appropriate.


2618-2652: LGTM! Developer billing params properly implemented.

The DeveloperBillingOptionParamsAndroid class is well-structured with comprehensive documentation. All three required parameters are properly typed and the JSON conversion handles appropriate defaults.


2808-2851: LGTM! Purchase props properly extended for external payments.

The developerBillingOption field is correctly added as optional to RequestPurchaseAndroidProps. The documentation clearly explains the external payments flow behavior when this parameter is provided.


3003-3072: LGTM! Subscription props properly extended for external payments.

The developerBillingOption field is correctly added to RequestSubscriptionAndroidProps, maintaining consistency with the purchase props. The implementation properly supports the external payments flow for subscriptions.

Comment thread packages/docs/src/pages/docs/types/alternative.tsx
Comment thread packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt Outdated
- Fix setLinkUri to use android.net.Uri instead of String
- Fix listener add/remove mismatch with remembered listeners
- Add Horizon guard for Play-only listeners
- Rename userChoiceBillingListener to userChoiceBillingListenerAndroid in docs

@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: 3

♻️ Duplicate comments (1)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)

1674-1676: Past issue resolved: setLinkUri now correctly uses android.net.Uri.

The previous review comment about using String instead of android.net.Uri has been addressed. Line 1675 now correctly reflects the method with android.net.Uri::class.java, and line 1676 properly parses the string to a Uri before invocation.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f433c0 and 726abe2.

📒 Files selected for processing (4)
  • packages/docs/src/pages/docs/features/external-purchase.tsx
  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: iOS-specific functions must end with IOS suffix (e.g., clearTransactionIOS, getAppTransactionIOS)
Android-specific functions must end with Android suffix (e.g., acknowledgePurchaseAndroid, consumePurchaseAndroid)
Use action prefixes for function naming: get for retrieval, request for async operations, clear for removal, is/has for boolean checks, show/present for UI display, begin/finish/end for process control

Prefer interface for defining object shapes in TypeScript

Files:

  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/docs/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*.{ts,tsx}: Use kebab-case for search modal IDs (e.g., id: 'request-products')
Modal state should be defined once at the app root level using Preact Signals (signal from '@preact/signals-react'), not instantiated multiple times
ALL components must fit within parent boundaries and never overflow outside parent containers; use overflow-hidden, break-words, and whitespace-nowrap as needed
Delete unused components, functions, and imports immediately; do not keep commented-out code or unused variables
ANY function that returns a Promise must be wrapped with void operator when used where a void return is expected (e.g., event handlers)

Files:

  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/docs/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

packages/docs/**/*: Before committing, run npx prettier --write, npm run lint, bun run tsc or npm run typecheck, and npm run build to verify formatting, linting, types, and build success
Use conventional commit format with lowercase type prefix and lowercase description (e.g., feat: add user authentication)

Files:

  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

**/*.{ts,tsx,js,jsx}: Use camelCase for variable and function names
Use PascalCase for class and component names
Always use async/await for handling promises instead of .then() chains
Add JSDoc comments for public functions and exported APIs
Use const by default, let if reassignment is needed, avoid var

Files:

  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/docs/src/pages/docs/features/external-purchase.tsx
packages/google/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

DO NOT add Android suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use acknowledgePurchase() not acknowledgePurchaseAndroid())

Files:

  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
🧠 Learnings (5)
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to **/*.{ts,tsx} : Android-specific functions must end with `Android` suffix (e.g., `acknowledgePurchaseAndroid`, `consumePurchaseAndroid`)

Applied to files:

  • packages/docs/src/pages/docs/types/alternative.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to **/*.{ts,tsx} : iOS-specific functions must end with `IOS` suffix (e.g., `clearTransactionIOS`, `getAppTransactionIOS`)

Applied to files:

  • packages/docs/src/pages/docs/types/alternative.tsx
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/**/*.kt : DO NOT add `Android` suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use `acknowledgePurchase()` not `acknowledgePurchaseAndroid()`)

Applied to files:

  • packages/docs/src/pages/docs/types/alternative.tsx
  • packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : DO NOT edit `openiap/src/main/Types.kt` as it is auto-generated from the GraphQL schema; regenerate it using `./scripts/generate-types.sh`

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-12-06T20:15:59.233Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helper functions in `openiap/src/main/java/dev/hyo/openiap/utils/`

Applied to files:

  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
🧬 Code graph analysis (2)
packages/docs/src/pages/docs/types/alternative.tsx (3)
packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt (1)
  • code (611-616)
packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt (2)
  • code (1843-1865)
  • code (2489-2508)
packages/gql/src/generated/Types.kt (2)
  • code (1916-1938)
  • code (2562-2581)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)
packages/gql/src/generated/types.ts (1)
  • DeveloperProvidedBillingDetailsAndroid (143-150)
🪛 detekt (1.23.8)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt

[warning] 1412-1412: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1423-1423: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1694-1694: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 1696-1696: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Test Android
🔇 Additional comments (15)
packages/docs/src/pages/docs/types/alternative.tsx (1)

195-532: Excellent comprehensive examples with proper listener naming.

The User Choice Billing and Alternative Billing Only examples are well-structured and comprehensive. The use of userChoiceBillingListenerAndroid (lines 207, 214, 253) correctly follows the Android-specific naming convention, addressing the previous review feedback.

The warning box about the 24-hour reporting requirement (lines 524-532) is appropriately placed and highlights a critical compliance point.

packages/docs/src/pages/docs/features/external-purchase.tsx (3)

780-847: User Choice Billing listener naming correctly updated.

All references to userChoiceBillingListenerAndroid (lines 784, 795, 839) now correctly include the Android suffix, addressing the previous review feedback.


1260-1640: Excellent comprehensive External Payments documentation.

The External Payments (8.3.0+) section is well-structured with:

  • Clear availability information (Japan only)
  • Comprehensive key differences explanation (lines 1279-1297)
  • Detailed flow diagram (lines 1544-1600)
  • Appropriate warning about 24-hour token reporting requirement (lines 1602-1612)
  • Clear launch mode options documentation (lines 1614-1639)

The multi-language code examples (TypeScript, Kotlin, Dart) provide excellent coverage for developers.


62-62: Platform support table correctly updated.

The Android platform entry now accurately reflects support for Google Play Billing 8.3.0+ with External Payments.

packages/google/Example/src/main/java/dev/hyo/martie/screens/AlternativeBillingScreen.kt (6)

42-45: LGTM! External Payments imports added correctly.

The new imports for External Payments (8.3.0+) support are properly added and necessary for the feature implementation.


54-54: ✅ Enum addition and remembered listeners fix past review issues.

The addition of EXTERNAL_PAYMENTS enum value and the creation of remembered listener instances properly address the listener lifecycle issues flagged in the previous review. Using remember ensures a single listener instance is created and can be properly added/removed without leaks or duplicates.

Based on learnings, this follows the correct pattern for listener management in this Android-only package.

Also applies to: 82-122


124-144: ✅ Past review issues resolved: Listener lifecycle and Horizon guards fixed.

Both critical issues from the previous review have been properly addressed:

  1. Listener add/remove pairing fixed: Now correctly uses addUserChoiceBillingListener/removeUserChoiceBillingListener and addDeveloperProvidedBillingListener/removeDeveloperProvidedBillingListener with the same remembered listener instance, preventing leaks and duplicate callbacks.

  2. Horizon guards added: Both LaunchedEffect blocks now check if (isHorizon) return@LaunchedEffect at the start, preventing UnsupportedOperationException on Horizon builds.


198-203: LGTM! External Payments connection config follows correct pattern.

The initialization logic for External Payments properly enables BillingProgramAndroid.ExternalPayments before connection, matching the pattern used for Billing Programs mode. This aligns with Google Play Billing 8.3.0+ requirements.


329-329: LGTM! Clear and informative UI updates for External Payments.

The UI additions properly communicate:

  • Japan-only availability (mentioned multiple times for clarity)
  • Side-by-side purchase choice mechanism
  • Detailed 7-step flow explanation
  • Differences from User Choice Billing

The use of the Language icon (🌐) and consistent "8.3.0+ Japan" labeling helps users understand the feature's scope and restrictions.

Also applies to: 406-425, 474-493, 539-555, 598-598


939-1016: Fix deprecated parameter: use google instead of android on line 902.

The code uses the correct, non-deprecated parameter name google on line 968, but the User Choice flow on line 902 still uses the deprecated android parameter. Both should use google:

Line 902 should be:

RequestPurchasePropsByPlatforms(
    google = RequestPurchaseAndroidProps(
        skus = listOf(selectedProduct!!.id)
    )
)

The android parameter is marked as deprecated across all generated types (Kotlin, TypeScript, Dart, Swift) in favor of google.

⛔ Skipped due to learnings
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to packages/google/**/*.kt : DO NOT add `Android` suffix to function names in the Android-only package, even for Android-specific APIs (e.g., use `acknowledgePurchase()` not `acknowledgePurchaseAndroid()`)
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T20:15:59.233Z
Learning: Applies to **/*.{ts,tsx} : Android-specific functions must end with `Android` suffix (e.g., `acknowledgePurchaseAndroid`, `consumePurchaseAndroid`)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (5)

1560-1633: Well-structured External Payments initialization.

The enableExternalPaymentsProgram method correctly:

  • Creates the DeveloperProvidedBillingListener proxy via reflection
  • Extracts and propagates the external transaction token
  • Uses appropriate exception handling for version compatibility (8.3.0+)
  • Provides comprehensive logging for debugging

1642-1701: Correct implementation of DeveloperBillingOption.

The applyDeveloperBillingOption method properly:

  • Converts enums to billing program constants
  • Builds DeveloperBillingOptionParams via reflection
  • Correctly uses android.net.Uri for the link URI parameter
  • Applies the params to the billing flow builder
  • Handles version compatibility gracefully

1396-1431: Billing programs initialization handles External Payments correctly.

The initialization logic properly:

  • Distinguishes EXTERNAL_PAYMENTS (8.3.0+) from other billing programs (8.2.0+)
  • Routes EXTERNAL_PAYMENTS to the specialized enableExternalPaymentsProgram with listener support
  • Uses the simpler enableBillingProgram API for other programs
  • Handles version compatibility with appropriate exception catching and logging

1196-1202: Listener management methods implemented correctly.

The addDeveloperProvidedBillingListener and removeDeveloperProvidedBillingListener methods properly manage the listener collection, following the established pattern for other listener types.


935-938: Developer billing option correctly integrated into purchase flow.

The conditional application of developerBillingOption is properly positioned in the billing flow setup, applying the external payments configuration only when specified in the purchase arguments.

Comment thread packages/docs/src/pages/docs/features/external-purchase.tsx
Comment thread packages/docs/src/pages/docs/types/alternative.tsx
- Rename developerProvidedBillingListener to developerProvidedBillingListenerAndroid in docs
- Notify legacy-style listeners (set via constructor or setter) in addition to collection listeners
Changed from com.android.billingclient:billing to
io.github.hyochan.openiap:openiap-google
@hyochan hyochan merged commit 849fff9 into main Dec 27, 2025
6 checks passed
@hyochan hyochan deleted the feature/external-payments-830 branch December 27, 2025 16:21
@hyochan hyochan changed the title feat(google): add External Payments (8.3.0+) support for Japan feat(google): add External Payments (8.3.0+) support for 🇯🇵 Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android 🎯 feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant