IMPORTANT: Since this is an Android-only package, DO NOT add Android suffix to function names, even for Android-specific APIs.
✅ Correct:
fun acknowledgePurchase()
fun consumePurchase()
fun getPackageName()
fun buildModule(context: Context)
fun isHorizonEnvironment(context: Context)❌ Incorrect:
fun acknowledgePurchaseAndroid() // Don't add Android suffix
fun consumePurchaseAndroid() // Don't add Android suffix
fun buildModuleAndroid() // Don't add Android suffixException: Generated GraphQL operation names and generated handler fields
must keep the schema name exactly, including Android when the operation is
Android-only. For example, MutationHandlers.checkAlternativeBillingAvailabilityAndroid
is generated from packages/gql/src/api-android.graphql and must be wired under
that name; the hand-written implementation it delegates to should still be
suffix-free, such as checkAlternativeBillingAvailability().
Only add Android suffix when the identifier is part of a cross-platform API
that has platform-specific variants (e.g., ProductAndroid, PurchaseAndroid
types that contrast with iOS types), or when it is a generated GraphQL
operation/handler identifier that must match the schema.
- Enum values in this codebase must use kebab-case (e.g.,
non-consumable,in-app,user-cancelled) - This matches the convention used in the auto-generated Types.kt from GraphQL schemas
- Do not use snake_case (e.g.,
non_consumable) or camelCase for enum raw values
openiap/src/main/java/dev/hyo/openiap/Types.ktis auto-generated. Regenerate it with./scripts/generate-types.shafter changing any GraphQL schema files.- Never edit
Types.ktmanually. Regeneration guarantees consistency across platforms and avoids merge conflicts. - When additional parsing or conversion helpers are needed for GraphQL payloads, place them in a utility file (for example
openiap/src/main/java/dev/hyo/openiap/utils/JsonUtils.kt). Keep all custom helpers outside of generated sources and have the hand-written code call into them.
- Shared helper extensions such as safe
Map<String, *>lookups must live in utility sources (utils/*.kt) so they can be reused without modifying generated files. - Utility files should include succinct KDoc explaining their intent and reference the convention above when interacting with generated code.
- The Android
OpenIapModuleexposes every GraphQL operation through the typealias handlers defined inTypes.kt(e.g.MutationInitConnectionHandler,QueryGetAvailablePurchasesHandler, etc.). - These handlers are declared as properties (for example
val initConnection = ...) insideOpenIapModule; they encapsulate all coroutine work (withContext,suspendCancellableCoroutine, etc.) and return the types required by the GraphQL schema (e.g.RequestPurchaseResult). OpenIapStoreand other consumers must call the module through these handler properties rather than direct suspend functions, unpacking any wrapper results (such asRequestPurchaseResultPurchases) as needed.- Keep helper wiring inside
OpenIapModule—avoid reintroducing extension builders likecreateQueryHandlers; the module itself ownsqueryHandlers,mutationHandlers, andsubscriptionHandlersvalues so wiring stays localized and in sync with the typealiases.
This package supports two build flavors:
| Flavor | Store | Source Directory |
|---|---|---|
play (default) |
Google Play Store | src/play/ |
horizon |
Meta Quest Store | src/horizon/ |
openiap/src/
├── main/ # Shared code (used by both flavors)
├── play/ # Play Store specific implementations
└── horizon/ # Meta Horizon specific implementations
src/main/: Code that works for BOTH Play and Horizonsrc/play/: Play Store specific code (Google Play Billing API)src/horizon/: Horizon specific code (Meta S2S API)
When modifying shared code in src/main/, ALWAYS test both flavors:
# Play flavor
./gradlew :openiap:compilePlayDebugKotlin
# Horizon flavor
./gradlew :openiap:compileHorizonDebugKotlinSome APIs exist only in Horizon flavor:
getAvailableItems- Fetch catalog items (Horizon only)VerifyPurchaseHorizonOptions- Horizon verification parametersVerifyPurchaseResultHorizon- Horizon verification result
- Run
./scripts/generate-types.shwhenever GraphQL schema definitions change. - After regenerating, run the relevant Gradle targets for BOTH flavors:
./gradlew :openiap:compilePlayDebugKotlin ./gradlew :openiap:compileHorizonDebugKotlin