Skip to content

Billing

VentureKit provides billing support through @venturekit-pro/billing with plan definitions, feature limits, and usage-to-invoice mapping.

Terminal window
npm install @venturekit-pro/billing@dev

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

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 },
],
},
]);

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'] });

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 generation

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();

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),
}),
],
});