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
Integrated Approach (Recommended)
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
Using HyveClient (Recommended)
// 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
Using HyveClient (Recommended)
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
- Add Stripe checkout container to your HTML:
<div id="stripe-checkout-element"></div>
- 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'
});
- Handle completion:
The SDK automatically detects when the user returns from Stripe checkout by checking for
?payment=completein the URL.
Native Integration (React Native)
- 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}
/>
- 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
Using HyveClient (Recommended)
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
userIdfrom authenticated user - Configures
gameIdfrom URL parameters - Uses the client's API base URL as
checkoutUrl - Preserves callbacks when reinitializing
Testing
Testing Web Billing
- Use Stripe test keys:
pk_test_... - Open your game with URL params:
http://localhost:3000?game-id=1&hyve-access=JWT_TOKEN - Test with Stripe test cards:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002
- Success:
Testing Native Billing
- Load your game in React Native WebView with URL params
- Use test IAP products from Google Play Console / App Store Connect
- Test purchase flow end-to-end
- 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
stripePublishableKeyis provided - Native: Ensure React Native WebView is set up correctly
- Check browser/native console for error messages
- Verify
checkoutUrlis accessible
Products Not Loading
Problem: getProducts() returns empty array or errors
Solutions:
- Verify
gameIdexists 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
- Error Handling: Always set up
onPurchaseErrorcallback - User Feedback: Show loading states during purchase flow
- Security: Never trust client-side purchase validation - always validate server-side
- Product IDs: Keep product IDs consistent across platforms
- Testing: Test both web and native flows before production
- Cleanup: Call
billing.dispose()when done to clean up resources - 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 purchaseoptions.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 purchaseoptions.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.