Horizon Billing sample overview
Updated: May 6, 2026
This sample demonstrates how to integrate in-app purchases (IAP) on Meta Quest using the Horizon Billing Compatibility SDK, alongside a parallel Google Play Billing implementation for Android mobile. It uses Android build variants and Hilt dependency injection to maintain a single codebase that targets both platforms, showing you how to handle durable items, consumables, and subscriptions with a unified billing abstraction.
Complete this guide to learn how to:
- create a platform-agnostic billing interface that abstracts Quest and mobile IAP implementations
- use Android build variants (product flavors) to compile different billing SDKs into separate APKs from shared source
- set up Hilt dependency injection to provide the correct billing handler implementation per platform
- implement purchase flows for durable items, consumables, and subscriptions
- query and display purchase history, acknowledge purchases, and consume consumable items
- Android development environment
- Meta Quest headset or Android mobile device
Clone the Meta Horizon Platform SDK Samples repository and open the HorizonBillingSample directory in Android Studio. The project includes two build variants: questDebug/questRelease for Meta Quest and mobileDebug/mobileRelease for Android mobile. Select the appropriate variant from the Build Variants panel, then build and run on your target device.
The project uses Android product flavors to organize platform-specific code while sharing all activities, data models, and UI logic.
| File / Directory | What it demonstrates | Key concepts |
|---|
| Shared code: activities, billing interface, data models, utilities | Core abstractions that work across platforms |
| Platform-agnostic billing interface with 6 methods | Strategy pattern for swappable implementations |
| Platform-independent product type enum (INAPP, SUBS) | Abstraction over platform-specific billing types |
| Main UI with 6 purchase buttons and view purchase history | Billing handler injection via Hilt |
| Quest-specific implementation using Horizon Billing SDK | setAppId() builder configuration |
| Mobile-specific implementation using Google Play Billing | enablePendingPurchases() builder configuration |
When you run this app, you see a main screen with six purchase buttons: free and paid versions of durable items, consumable items, and subscriptions. The screen displays the current build variant and device information. Tapping a purchase button queries product details from the platform billing service, then launches the system billing UI. After a successful purchase, the app acknowledges the purchase automatically and displays a completion screen with the item name, quantity, and order date. The View Purchases button opens a history screen showing all INAPP purchases with a Consume button next to consumable items.
The sample defines IBillingHandler with six methods that cover the complete purchase lifecycle: initialize(), requestProductDetails(), requestPurchases(), launchBillingFlow(), acknowledgePurchase(), and consumePurchase(). Each platform implements this interface using its respective billing SDK. The interface returns List<Any> from requestProductDetails() because the ProductDetails class differs between platforms — each handler casts internally when launching the billing flow.
Build variant strategy with Hilt injection
The sample uses a devices flavor dimension with mobile and quest flavors. Each flavor has its own source set (app/src/quest/ and app/src/mobile/) containing a Hilt module that provides IBillingHandler. Because each module lives in its respective source set, only one is compiled into any given build:
@Module
@InstallIn(SingletonComponent::class)
class QuestAppPurchaseModule {
@Singleton @Provides
fun provideAppPurchaseHandler(): IBillingHandler = QuestAppPurchaseHandler()
}
Activities inject the handler using @Inject lateinit var billingHandler: IBillingHandler. Hilt automatically provides the correct implementation based on the build variant.
How Horizon Billing mirrors Google Play Billing
The Horizon Billing Compatibility SDK mirrors the Google Play Billing API. Class names, method signatures, builder patterns, and response codes are identical. The main difference is the builder configuration: Quest uses setAppId(BuildConfig.QUEST_APP_ID) while mobile uses enablePendingPurchases(PendingPurchasesParams). This design minimizes platform-specific code.
Both handlers cover all 12 BillingResponseCode values: OK, ERROR, BILLING_UNAVAILABLE, DEVELOPER_ERROR, FEATURE_NOT_SUPPORTED, ITEM_ALREADY_OWNED, ITEM_NOT_OWNED, ITEM_UNAVAILABLE, NETWORK_ERROR, SERVICE_DISCONNECTED, SERVICE_UNAVAILABLE, USER_CANCELED, plus a default branch.
Purchase acknowledgment and consumption
The sample handles acknowledgment automatically after a successful purchase. For consumable items, the ViewPurchasesActivity displays a Consume button that calls billingHandler.consumePurchase(). The sample determines whether an item is consumable by checking if the product name contains “consume” — a heuristic suitable for demonstration purposes.
- Add a dedicated subscriptions view that queries SUBS purchases separately and displays subscription details, expiration dates, and renewal status.
- Implement a more robust consumable detection mechanism by adding a product metadata flag or configuration file instead of relying on product name patterns.
- Create a third build variant targeting a different platform to further demonstrate the flexibility of the abstraction layer.