Skip to main content

Hyve SDK - Billing Integration

The Hyve SDK provides a unified billing system that automatically detects whether your game is running on web or native platforms and routes to the appropriate payment method:

  • Web: Stripe Embedded Checkout
  • Native: In-App Purchases (IAP) via Google Play / App Store

Installation

npm install @hyve-sdk/js
# or
pnpm add @hyve-sdk/js
# or
yarn add @hyve-sdk/js

Quick Start

Use HyveClient with built-in billing support:

import { HyveClient } from '@hyve-sdk/js';

// Initialize client with billing config
const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...' // Required for web
}
});

// Authenticate from URL (extracts user and game info)
await client.authenticateFromUrl();

// Initialize billing (auto-configures with user/game data)
await client.initializeBilling();

// Check which platform we're on
const platform = client.getBillingPlatform();
console.log('Platform:', platform); // "web" | "native" | "unknown"

// Check if billing is available
const available = client.isBillingAvailable();
console.log('Billing available:', available);

Standalone Approach

For advanced use cases, use BillingService directly:

import { BillingService, BillingPlatform } from '@hyve-sdk/js';

// Initialize billing service
const billing = new BillingService({
stripePublishableKey: 'pk_test_...', // Required for web
checkoutUrl: 'https://your-api.com', // Your billing API endpoint
gameId: 123, // Your game ID
userId: 'user_456' // Current user ID
});

// Initialize the service
await billing.initialize();

// Check which platform we're on
const platform = billing.getPlatform();
console.log('Platform:', platform); // "web" | "native" | "unknown"

Fetching Products

// Get available products for the current platform
const products = await client.getBillingProducts();

products.forEach(product => {
console.log({
productId: product.productId,
title: product.title,
description: product.description,
price: product.price,
localizedPrice: product.localizedPrice,
currency: product.currency
});
});

Using BillingService Directly

const products = await billing.getProducts();

Making a Purchase

For web purchases, add a DOM element to mount the Stripe checkout form:

<!-- Add this to your HTML -->
<div id="stripe-checkout-element"></div>
// Set up purchase event handlers
client.onPurchaseComplete((result) => {
console.log('Purchase successful!', {
productId: result.productId,
transactionId: result.transactionId,
transactionDate: result.transactionDate
});
});

client.onPurchaseError((result) => {
console.error('Purchase failed:', {
productId: result.productId,
error: result.error
});
});

// Initiate purchase
// Web: Mounts Stripe checkout form
// Native: Shows native store UI
await client.purchaseProduct('price_1234', {
elementId: 'stripe-checkout-element' // Optional, defaults to 'stripe-checkout-element'
});

// To cancel/unmount the checkout form (web only)
client.unmountBillingCheckout();

Using BillingService Directly

For native purchases or standalone billing:

// Set up purchase event handlers
billing.onPurchaseComplete((result) => {
console.log('Purchase successful!', result);
});

billing.onPurchaseError((result) => {
console.error('Purchase failed:', result.error);
});

// Initiate purchase (shows native store UI or mounts Stripe checkout)
await billing.purchase('price_1234', {
elementId: 'stripe-checkout-element' // Web only, optional
});

// To cancel/unmount the checkout form
billing.unmountCheckoutElement();

Configuration

Using HyveClient

When using HyveClient, billing auto-configures with user and game data from authentication:

const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...' // Only this is required
// userId, gameId, and checkoutUrl are auto-configured
}
});

await client.authenticateFromUrl(); // Extracts user and game info
await client.initializeBilling(); // Auto-configures billing

You can also manually configure billing after initialization:

client.configureBilling({
stripePublishableKey: 'pk_live_...',
// Override auto-configured values if needed
gameId: 456,
userId: 'custom_user_id',
checkoutUrl: 'https://custom-api.com'
});

await client.initializeBilling();

Using BillingService Directly

interface BillingConfig {
/**
* Stripe publishable key (required for web)
* Get from: https://dashboard.stripe.com/test/apikeys
*/
stripePublishableKey?: string;

/**
* Your game's unique identifier (required)
* Used to fetch products from your API
*/
gameId?: number;

/**
* Current user ID (required)
* Used for purchase attribution
*/
userId?: string;

/**
* Your billing API endpoint (required)
* Must implement required endpoints (see API Requirements)
*/
checkoutUrl?: string;
}

Platform Detection

The SDK automatically detects the platform:

const platform = billing.getPlatform();

if (platform === BillingPlatform.WEB) {
// Running in browser - will use Stripe
console.log('Using Stripe for payments');
} else if (platform === BillingPlatform.NATIVE) {
// Running in React Native WebView - will use IAP
console.log('Using native In-App Purchases');
} else {
// Platform unknown
console.warn('Platform not detected');
}

API Types

BillingProduct

interface BillingProduct {
productId: string; // Product/Price ID
title: string; // Product name
description: string; // Product description
price: number; // Numeric price
localizedPrice: string; // Formatted price (e.g., "$9.99")
currency: string; // Currency code (e.g., "USD")
}

PurchaseResult

interface PurchaseResult {
success: boolean; // Whether purchase succeeded
productId: string; // Product that was purchased
transactionId?: string; // Transaction/receipt ID
transactionDate?: string; // Purchase timestamp
error?: {
code: string; // Error code
message: string; // Error message
};
}

Server-Side Requirements

Your billing API must implement the following endpoints:

GET /get-packages

Returns products for web (Stripe) purchases.

Query Parameters:

  • game_id - Your game ID

Response:

{
"packages": [
{
"priceId": "price_1234567890",
"productId": "prod_1234567890",
"game_id": "1",
"game_name": "My Game",
"package_name": "1000 Gold Coins",
"price_display": "$9.99",
"price_cents": 999,
"items": [
{ "key": "gold", "amount": 1000 }
]
}
]
}

Note: For web packages, use priceId (Stripe Price ID) instead of productId.

GET /get-native-packages

Returns products for native IAP purchases.

Query Parameters:

  • game_id - Your game ID

Response:

{
"packages": [
{
"productId": "com.game.gold.1000",
"game_id": "1",
"game_name": "My Game",
"package_name": "1000 Gold Coins",
"price_display": "$9.99",
"price_cents": 999,
"items": [
{ "key": "gold", "amount": 1000 }
]
}
]
}

Note: For native packages, productId must match the product ID in Google Play / App Store.

POST /create-checkout-session

Creates a Stripe checkout session for web purchases.

Request Body:

{
"priceId": "price_1234567890",
"userId": "user_123",
"gameId": 1,
"embedded": true,
"return_url": "https://yourapp.com?payment=complete"
}

Response:

{
"client_secret": "cs_test_...",
"id": "cs_1234567890"
}

POST /iap/validate

Validates native IAP receipts (called automatically by the SDK via native app).

Request Body:

{
"userId": "user_123",
"gameId": 1,
"platform": "android", // or "ios"
"productId": "com.game.gold.1000",
"transactionId": "GPA.1234-5678-9012-34567",
"purchaseToken": "abc123...",
"transactionDate": "2024-01-15T10:30:00Z"
}

Response:

{
"valid": true,
"credited": true
}

Platform-Specific Integration

Web Integration

  1. Add Stripe checkout container to your HTML:
<div id="stripe-checkout-element"></div>
  1. Initialize and purchase:
const billing = new BillingService({
stripePublishableKey: 'pk_test_...',
checkoutUrl: 'https://your-api.com',
gameId: 123,
userId: 'user_456'
});

await billing.initialize();

// Mount checkout form when user clicks purchase
await billing.purchase('price_1234', {
elementId: 'stripe-checkout-element'
});
  1. Handle completion: The SDK automatically detects when the user returns from Stripe checkout by checking for ?payment=complete in the URL.

Native Integration (React Native)

  1. Set up WebView in React Native:
import { WebView } from 'react-native-webview';

<WebView
source={{ uri: 'https://yourgame.com?game-id=123&hyve-access=JWT' }}
onMessage={handleWebViewMessage}
/>
  1. Handle native messages:
const handleWebViewMessage = (event) => {
const message = JSON.parse(event.nativeEvent.data);

switch (message.type) {
case 'CHECK_IAP_AVAILABILITY':
// Check if IAP is available
const available = await checkIAPAvailability();
webViewRef.current?.injectJavaScript(`
window.dispatchEvent(new MessageEvent('message', {
data: {
type: 'IAP_AVAILABILITY_RESULT',
payload: { available: ${available} }
}
}));
`);
break;

case 'PURCHASE':
// Initiate purchase
const { productId, userId } = message.payload;
const result = await purchaseProduct(productId);

if (result.success) {
webViewRef.current?.injectJavaScript(`
window.dispatchEvent(new MessageEvent('message', {
data: {
type: 'PURCHASE_COMPLETE',
payload: {
productId: '${productId}',
transactionId: '${result.transactionId}',
transactionDate: '${result.transactionDate}'
}
}
}));
`);
} else {
webViewRef.current?.injectJavaScript(`
window.dispatchEvent(new MessageEvent('message', {
data: {
type: 'PURCHASE_ERROR',
payload: {
code: '${result.errorCode}',
message: '${result.errorMessage}',
productId: '${productId}'
}
}
}));
`);
}
break;
}
};

Advanced Usage

Custom Logging

Capture all SDK logs for debugging or analytics:

Using HyveClient

client.onBillingLog((level, message, data) => {
console.log(`[${level.toUpperCase()}]`, message, data);

// Send to your analytics service
if (level === 'error') {
analytics.trackError(message, data);
}
});

Using BillingService Directly

billing.onLog((level, message, data) => {
console.log(`[${level.toUpperCase()}]`, message, data);

// Send to your analytics service
if (level === 'error') {
analytics.trackError(message, data);
}
});

Complete Purchase Flow Example

import { HyveClient, BillingPlatform, type PurchaseResult } from '@hyve-sdk/js';

class GameBilling {
private client: HyveClient;

async initialize() {
// Initialize client with billing
this.client = new HyveClient({
billing: {
stripePublishableKey: process.env.STRIPE_PUBLIC_KEY
}
});

// Authenticate from URL (auto-extracts user and game info)
await this.client.authenticateFromUrl();

// Set up logging
this.client.onBillingLog((level, message, data) => {
console.log(`[Billing ${level}]`, message, data);
});

// Set up purchase handlers
this.client.onPurchaseComplete((result) => {
this.handlePurchaseSuccess(result);
});

this.client.onPurchaseError((result) => {
this.handlePurchaseError(result);
});

// Initialize billing (auto-configures with user/game data)
await this.client.initializeBilling();

// Log platform
const platform = this.client.getBillingPlatform();
console.log('Billing platform:', platform);

return this.client.isBillingAvailable();
}

async loadStore() {
const products = await this.client.getBillingProducts();

// Display products in your UI
this.renderProducts(products);

return products;
}

async buyProduct(productId: string) {
const platform = this.client.getBillingPlatform();

if (platform === BillingPlatform.WEB) {
// Show checkout modal/container
this.showCheckoutModal();

// Mount Stripe checkout
await this.client.purchaseProduct(productId, {
elementId: 'checkout-container'
});
} else {
// Native - just purchase (shows native UI)
await this.client.purchaseProduct(productId);
}
}

private handlePurchaseSuccess(result: PurchaseResult) {
console.log('Purchase successful!', result);

// Update UI
this.showSuccessMessage('Thank you for your purchase!');

// Credit items to user (if not done server-side)
this.creditPurchasedItems(result.productId);

// Close checkout UI
this.hideCheckoutModal();
}

private handlePurchaseError(result: PurchaseResult) {
console.error('Purchase failed:', result.error);

// Show error to user
this.showErrorMessage(
result.error?.message || 'Purchase failed. Please try again.'
);

// Close checkout UI
this.hideCheckoutModal();
}

cleanup() {
// Client cleanup (if needed)
this.client.reset();
}
}

Integrated Usage with HyveClient

The recommended approach is to use billing directly through HyveClient:

import { HyveClient } from '@hyve-sdk/js';

// Initialize with billing config
const client = new HyveClient({
billing: {
stripePublishableKey: 'pk_test_...'
}
});

// Authenticate and initialize billing
await client.authenticateFromUrl(); // Auto-extracts user and game info
await client.initializeBilling(); // Auto-configures billing with user/game data

// Use billing features
const products = await client.getBillingProducts();
await client.purchaseProduct(productId);

This approach automatically:

  • Configures userId from authenticated user
  • Configures gameId from URL parameters
  • Uses the client's API base URL as checkoutUrl
  • Preserves callbacks when reinitializing

Testing

Testing Web Billing

  1. Use Stripe test keys: pk_test_...
  2. Open your game with URL params:
    http://localhost:3000?game-id=1&hyve-access=JWT_TOKEN
  3. Test with Stripe test cards:
    • Success: 4242 4242 4242 4242
    • Decline: 4000 0000 0000 0002

Testing Native Billing

  1. Load your game in React Native WebView with URL params
  2. Use test IAP products from Google Play Console / App Store Connect
  3. Test purchase flow end-to-end
  4. Verify receipt validation on your server

Example Test Application

See the complete example in apps/native-bridge-tester:

cd apps/native-bridge-tester
pnpm dev
# Open http://localhost:3002?game-id=1

Troubleshooting

Initialization Fails

Problem: billing.initialize() returns false

Solutions:

  • Web: Ensure stripePublishableKey is provided
  • Native: Ensure React Native WebView is set up correctly
  • Check browser/native console for error messages
  • Verify checkoutUrl is accessible

Products Not Loading

Problem: getProducts() returns empty array or errors

Solutions:

  • Verify gameId exists in your database
  • Check API endpoint returns valid response format
  • Ensure products are configured for the game
  • Verify network connectivity

Purchase Fails

Problem: Purchase callback fires with error

Solutions:

  • Web: Verify Stripe key is valid (test vs live)
  • Native: Ensure app is installed from store (not development build)
  • Check product IDs match between platforms
  • Verify user has payment method set up
  • Check server-side validation logs

Platform Shows "UNKNOWN"

Problem: getPlatform() returns BillingPlatform.UNKNOWN

Solutions:

  • This shouldn't happen in normal operation
  • Check if running in unsupported environment
  • Verify WebView setup for native
  • Check browser compatibility for web

Best Practices

  1. Error Handling: Always set up onPurchaseError callback
  2. User Feedback: Show loading states during purchase flow
  3. Security: Never trust client-side purchase validation - always validate server-side
  4. Product IDs: Keep product IDs consistent across platforms
  5. Testing: Test both web and native flows before production
  6. Cleanup: Call billing.dispose() when done to clean up resources
  7. Logging: Use onLog() callback to capture and debug issues

API Reference

HyveClient Billing Methods

configureBilling(config: BillingConfig): void

Configure or update billing settings.

async initializeBilling(): Promise<boolean>

Initialize billing service. Auto-configures with user and game data from authentication. Returns true if initialization succeeded.

getBillingPlatform(): BillingPlatform

Returns the detected platform: "web", "native", or "unknown".

isBillingAvailable(): boolean

Returns true if billing is available on the current platform.

async getBillingProducts(): Promise<BillingProduct[]>

Fetches available products for the current platform.

async purchaseProduct(productId: string, options?: { elementId?: string }): Promise<PurchaseResult>

Initiates a purchase.

  • productId - The product/price ID to purchase
  • options.elementId - (Web only) DOM element ID to mount Stripe checkout

onPurchaseComplete(callback: (result: PurchaseResult) => void): void

Sets callback for successful purchases.

onPurchaseError(callback: (result: PurchaseResult) => void): void

Sets callback for failed purchases.

onBillingLog(callback: (level, message, data) => void): void

Sets callback to capture billing logs.

unmountBillingCheckout(): void

(Web only) Unmounts and destroys the Stripe checkout element.

BillingService Methods (Standalone)

constructor(config: BillingConfig)

Creates a new billing service instance.

async initialize(): Promise<boolean>

Initializes the billing service. Must be called before other methods. Returns true if initialization succeeded.

getPlatform(): BillingPlatform

Returns the detected platform: "web", "native", or "unknown".

isAvailable(): boolean

Returns true if billing is available on the current platform.

async getProducts(): Promise<BillingProduct[]>

Fetches available products for the current platform.

async purchase(productId: string, options?: { elementId?: string }): Promise<PurchaseResult>

Initiates a purchase.

  • productId - The product/price ID to purchase
  • options.elementId - (Web only) DOM element ID to mount Stripe checkout

onPurchaseComplete(callback: (result: PurchaseResult) => void): void

Sets callback for successful purchases.

onPurchaseError(callback: (result: PurchaseResult) => void): void

Sets callback for failed purchases.

onLog(callback: (level, message, data) => void): void

Sets callback to capture SDK logs.

unmountCheckoutElement(): void

(Web only) Unmounts and destroys the Stripe checkout element.

dispose(): void

Cleans up resources. Call when billing is no longer needed.