Billing
VentureKit provides billing support through @venturekit-pro/billing with plan definitions, feature limits, and usage-to-invoice mapping.
npm install @venturekit-pro/billing@devOverview
Section titled “Overview”@venturekit-pro/billing handles plan logic and invoicing, not payment processing. It provides:
- Plan definitions with feature limits
- Feature checking (
hasFeature,getFeatureLimit) - Usage-to-invoice-item mapping
- Auto-migration support for billing database tables
Integrate any payment provider separately when needed.
Defining Plans
Section titled “Defining Plans”import { definePlans } from '@venturekit-pro/billing';
// Each plan requires id/name/price/currency/interval/active and a `features`// array. Each feature is `{ featureKey, enabled, limit?, unitPrice?, description }`.// Use `limit: -1` for unlimited numeric features. Boolean features set// `enabled: true` (no `limit`).const plans = definePlans([ { id: 'free', name: 'Free', price: 0, currency: 'USD', interval: 'month', active: true, features: [ { featureKey: 'projects', enabled: true, limit: 3, description: 'Projects' }, { featureKey: 'storage', enabled: true, limit: 1_000_000_000, description: 'Storage (bytes)' }, { featureKey: 'apiRequests', enabled: true, limit: 10_000, description: 'API requests / month' }, { featureKey: 'customDomains', enabled: false, description: 'Custom domains' }, { featureKey: 'support', enabled: false, description: 'Priority support' }, ], }, { id: 'pro', name: 'Pro', price: 2900, currency: 'USD', interval: 'month', active: true, features: [ { featureKey: 'projects', enabled: true, limit: 50 }, { featureKey: 'storage', enabled: true, limit: 100_000_000_000 }, { featureKey: 'apiRequests', enabled: true, limit: 1_000_000 }, { featureKey: 'customDomains', enabled: true }, { featureKey: 'support', enabled: true }, ], }, { id: 'enterprise', name: 'Enterprise', price: 0, currency: 'USD', interval: 'year', active: true, // "contact us" — 0 = priced manually features: [ { featureKey: 'projects', enabled: true, limit: -1 }, // -1 = unlimited { featureKey: 'storage', enabled: true, limit: -1 }, { featureKey: 'apiRequests', enabled: true, limit: -1 }, { featureKey: 'customDomains', enabled: true }, { featureKey: 'support', enabled: true }, ], },]);Feature Checking
Section titled “Feature Checking”Use in handlers to enforce plan limits:
import { handler, ForbiddenError } from '@venturekit/runtime';import { getFeatureLimit, hasFeature } from '@venturekit-pro/billing';
export const main = handler(async (_body, ctx, logger) => { const userPlanId = 'free'; // Resolve from tenant/user const plan = plans.find(p => p.id === userPlanId); if (!plan) throw new ForbiddenError('Unknown plan');
// Check boolean feature — (plan, featureKey) if (!hasFeature(plan, 'customDomains')) { throw new ForbiddenError('Custom domains require a Pro plan'); }
// Check numeric limit — (plan, featureKey) const maxProjects = getFeatureLimit(plan, 'projects') ?? 0; const currentCount = await getProjectCount(ctx.user!.id); if (maxProjects !== -1 && currentCount >= maxProjects) { throw new ForbiddenError(`Project limit reached (${maxProjects})`); }
return { allowed: true };}, { scopes: ['projects.write'] });Usage-to-Invoice Mapping
Section titled “Usage-to-Invoice Mapping”Generate invoice line items from usage data:
import { mapUsageToLineItems } from '@venturekit-pro/billing';
// (plan, usage) — usage is a Record<featureKey, quantity>const lineItems = mapUsageToLineItems(plan, { apiRequests: 8_500, storage: 12_000_000_000,});// Returns structured line items for invoice generationAuto-Migrations
Section titled “Auto-Migrations”When @venturekit-pro/billing is installed, vk migrate discovers and applies billing-related database tables automatically:
import { getBillingMigrationsDir } from '@venturekit-pro/billing';
const migrationsDir = getBillingMigrationsDir();Combining with Tenancy
Section titled “Combining with Tenancy”Billing works well with @venturekit-pro/tenancy for per-tenant plan enforcement:
import { createTenantMiddleware, createQuotaMiddleware } from '@venturekit-pro/tenancy';import { getFeatureLimit } from '@venturekit-pro/billing';
export const main = handler(async (_body, ctx, logger) => { const tenant = ctx.tenant!; const planId = tenant.metadata.plan as string; const plan = plans.find(p => p.id === planId)!; const limit = getFeatureLimit(plan, 'apiRequests');
return { tenantId: tenant.id, plan: plan.id, apiRequestLimit: limit };}, { scopes: ['api.read'], middleware: [ createTenantMiddleware({ // `strategy` = data isolation model · `resolution` = how the tenant id is extracted config: { strategy: 'shared', resolution: 'subdomain' }, lookupTenant: async (id) => loadTenant(id), }), createQuotaMiddleware({ quotaKey: 'apiRequests', checkUsage: async (tenantId) => getMonthlyUsage(tenantId), }), ],});