Documentation
Frontend SDKs
JS/TS SDK, React bindings, and the embedded admin dashboard.
Frontend
Three companion frontend pieces ship alongside the server module.
@stackhouse/js (JS/TS SDK)
Location: stackhouse/js-sdks/stackhouse-js
import { createClient } from '@stackhouse/js';
const stackhouse = createClient('http://localhost:3000');
await stackhouse.signIn(email, password);
// Customer
const info = await stackhouse.billing.getCustomerInfo(appId, 'user-42');
if (await stackhouse.billing.hasEntitlement(appId, 'user-42', 'pro')) { /* unlock */ }
await stackhouse.billing.submitAppleReceipt(appId, 'user-42', receiptDataB64);
// Per-user experiment/paywall resolution
const resolved = await stackhouse.billing.getResolvedOffering(appId, 'user-42', {
country: 'US',
app_version: '1.2.0',
});
await stackhouse.billing.trackImpression(appId, 'user-42');
await stackhouse.billing.trackConversion(appId, 'user-42');
// Admin (requires service-admin JWT)
await stackhouse.billing.admin.upsertProduct({
app_id: appId, store: 'app_store', store_product_id: 'pro.monthly',
});
// Growth suite admin
await stackhouse.billing.admin.upsertAudience({
app_id: appId,
identifier: 'us-users',
rules: [{ field: 'country', op: 'eq', value: 'US' }],
});
await stackhouse.billing.admin.upsertExperiment({
app_id: appId,
identifier: 'price-test',
metric: 'purchase',
variants: [
{ identifier: 'control', offering_id: 1, is_control: true, traffic_weight: 50 },
{ identifier: 'treatment', offering_id: 2, is_control: false, traffic_weight: 50 },
],
});
const paywall = await stackhouse.billing.admin.upsertPaywall({
offering_id: 1,
template: 'default',
draft_config: { sections: [{ type: 'text', title: 'Go Pro', body: 'Unlock everything.' }] },
});@stackhouse/react (React bindings)
Location: stackhouse/js-sdks/stackhouse-react
import { BillingProvider, EntitlementGate, Paywall } from '@stackhouse/react';
<BillingProvider appId={42} appUserId="user-42">
<EntitlementGate identifier="pro" fallback={<Paywall onSelectPackage={buy} />}>
<ProOnlyFeature />
</EntitlementGate>
</BillingProvider>useResolvedOffering / useExperimentConversion are now exported
Both hooks are implemented in stackhouse/js-sdks/stackhouse-react/src/billing.tsx and
re-exported from @stackhouse/react since the export gap was closed. import { useResolvedOffering, useExperimentConversion } from '@stackhouse/react' now
works. The experiment-aware Paywall example below is live usage.
function MyPaywall() {
const track = useExperimentConversion(42, 'user-42');
return (
<Paywall
appId={42}
appUserId="user-42"
context={{ country: 'US' }}
onSelectPackage={(pkg, offering) => startPurchase(pkg, offering)}
onConversion={track}
/>
);
}Exports from @stackhouse/react today: BillingProvider, useOfferings, useEntitlements, useCustomerInfo, useHasEntitlement, useResolvedOffering, useExperimentConversion, <EntitlementGate>, <Paywall>.
Admin dashboard
The billing admin UI is embedded in the main Stackhouse Explore app at /admin/billing.
It is built from stackhouse/ui and included in the Rust binary via rust-embed when
you build stackhouse/ui and then rebuild the Rust server.
cd stackhouse/ui
npm run build
cd ..
cargo build --releaseThe dashboard manages apps, secrets, products, entitlements, offerings, audiences, experiments, paywalls, promo grants, and outbound webhook endpoints.
Tests
Unit tests (no DB required):
cargo test --lib billingCovers the entitlement resolver, audience matching, experiment bucketing, Stripe signature round-trip, Apple JWS decoding, and webhook payload signing/filtering.