Develop

Horizon Billing sample overview

Updated: May 8, 2026

Overview

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.

Learning objectives

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

Requirements

To run this sample, you need an Android development environment and either an Android mobile device or a Meta Quest headset. For platform setup instructions, see the Horizon Platform SDK documentation.

Get started

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. For detailed build instructions and configuration requirements, see the sample’s README.

Explore the sample

File / DirectoryWhat it demonstratesKey concepts
app/src/main/
Shared code: activities, billing interface, data models, utilities
Core abstractions that work across platforms
purchase/IBillingHandler.kt
Platform-agnostic billing interface with 6 methods
Strategy pattern for swappable implementations
purchase/CoreProductType.kt
Platform-independent product type enum (INAPP, SUBS)
Abstraction over platform-specific billing types
activities/MainActivity.kt
Main UI with 6 purchase buttons and view purchase history
Billing handler injection via Hilt
activities/ViewPurchasesActivity.kt
Purchase history screen with Consume button for consumables
Querying and consuming purchases
activities/PurchaseCompleteActivity.kt
Success screen showing order details
Purchase acknowledgment flow
activities/PurchaseErrorActivity.kt
Error handling with mapped response codes
BillingResponseCode interpretation
app/src/quest/QuestAppPurchaseHandler.kt
Quest-specific implementation using Horizon Billing SDK
setAppId() builder configuration
app/src/mobile/MobileAppPurchaseHandler.kt
Mobile-specific implementation using Google Play Billing
enablePendingPurchases() builder configuration
HorizonBillingSampleApplication.kt
Hilt application class
Dependency injection setup

Runtime behavior

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. Clicking 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.

Key concepts

Platform-agnostic billing interface

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. See the complete interface in purchase/IBillingHandler.kt.

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. See quest/QuestAppPurchaseHandler.kt and mobile/MobileAppPurchaseHandler.kt for the complete module definitions.

Horizon Billing API mirroring

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 consumability by checking if the product name contains “consume” — a heuristic suitable for demonstration purposes. See the acknowledgment logic in activities/MainActivity.kt and the consumption flow in activities/ViewPurchasesActivity.kt.

Extend the sample

  • 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 (such as a desktop VR headset) to further demonstrate the flexibility of the abstraction layer