Skip to content

@venturekit-pro/billing API

FunctionSignatureDescription
definePlans(plans: PlanDefinition[]) => PlansDefine billing plans with feature limits
getFeatureLimit(plans: Plans, planId: string, feature: string) => numberGet numeric limit for a feature
hasFeature(plans: Plans, planId: string, feature: string) => booleanCheck if a plan has a boolean feature
mapUsageToLineItems(usage, features) => LineItem[]Map usage data to invoice line items

createSubscriptionManager({ store, plans, defaultTrialDays? }) manages the subscription lifecycle over a pluggable SubscriptionStore:

MethodDescription
create(input)Start a subscription (CreateSubscriptionInput), optionally on trial
get(id) / getByCustomer(customerId)Look-ups
changePlan(subscriptionId, newPlanId, { immediate? })Upgrade / downgrade — immediately or at period end
previewProration(subscriptionId, newPlanId)What changePlan would charge or credit (ProrationResult)
cancel(subscriptionId, { immediate?, reason? })Cancel now or at period end
pause(subscriptionId) / resume(subscriptionId)Suspend billing without cancelling
FunctionDescription
calculateProration(currentPlan, newPlan, periodStart, periodEnd, changeDate?)Pure proration math → ProrationResult
createMemorySubscriptionStore()In-memory SubscriptionStore (create, get, getByCustomer, update, delete) for tests / prototypes

createUsageTracker(store) records metered usage — API calls, storage, seats, any custom metric — and aggregates it for invoicing:

MethodDescription
record(entry)Append a UsageRecord
increment(customerId, featureKey)+1
set(customerId, featureKey, value)Absolute value (gauges such as seats)
getUsage(customerId, featureKey, from, to)Total for a feature in a window
getSummary(customerId, from, to)Every feature’s total for a period

createMemoryUsageStore() is the in-memory UsageStore (record, query, aggregate).

createFeatureGate({ plans, getUsage? }) answers “may this customer do this?” against plan limits:

MethodDescription
check(planId, featureKey)FeatureCheckResult { allowed, reason?, limit? } for boolean / unlimited features
checkUsage(planId, featureKey, customerId)Same plus currentUsage / remaining from getUsage
require(planId, featureKey) / requireUsage(…)Throwing variants for handlers

What a tenant (or customer) may do right now: its plan’s features plus per-subject overrides (a negotiated “500 seats on Pro”, a temporary grant), cached per subject, and enforceable from a route with the right HTTP answer. EntitlementError carries the runtime’s structural error marker, so @venturekit/runtime’s boundary renders it as { error: { code, message, details } } with the proper status without this package depending on the runtime.

ExportDescription
createEntitlementsResolver({ plans, getPlanId, getOverrides?, ttlMs?, now? })EntitlementsResolver — forSubject(subjectId) resolves the subject’s plan (getPlanId is your lookup: subscription row, tenant column; null = no plan) and overrides, cached per subject (ttlMs default 60 000, 0 disables); invalidate(subjectId?) after a plan change
resolveEntitlements({ subjectId, plan, overrides? })The pure resolution behind it → Entitlements: plan features first, then overrides on top (source: 'plan' | 'override')
EntitlementOverridesRecord<featureKey, { enabled?, limit? }> — limit: null means unlimited
EntitlementErrorcode PLAN_UPGRADE_REQUIRED (402, feature not in plan), PLAN_LIMIT_REACHED (403, details.limit / details.usage for the upgrade prompt) or PLAN_UNKNOWN (403, no active plan); details.planId / details.featureKey always

Entitlements methods:

MethodDescription
has(featureKey)Enabled in plan or override
limitOf(featureKey)number, null (unlimited) or undefined (feature not available)
require(featureKey)Throws EntitlementError when not available
checkLimit(featureKey, usage)LimitCheck { allowed, limit, usage, remaining } — allowed is also true for unlimited
requireWithinLimit(featureKey, getUsage)Fetches usage only when the limit is finite, then enforces (403 at the cap, 402 when not in plan); returns the LimitCheck for headers / UI hints
toQuotas(keyMap?)The finite limits as a plain map — the shape @venturekit-pro/tenancy’s TenantContext.quotas / createQuotaMiddleware reads; keyMap renames ({ seats: 'maxUsers' })
import { createEntitlementsResolver } from '@venturekit-pro/billing';
const entitlements = createEntitlementsResolver({
plans,
getPlanId: (tenantId) => subscriptions.currentPlanId(tenantId),
getOverrides: (tenantId) => tenantSettings.get(tenantId).then((s) => s.planOverrides),
});
// in a route
const ent = await entitlements.forSubject(ctx.tenant!.id);
ent.require('api_access'); // 402 if not in plan
await ent.requireWithinLimit('seats', () => members.count(ctx.tenant!.id)); // 403 at the cap

createInvoiceGenerator({ store, prefix?, taxRate?, defaultCurrency?, defaultDueDays? }):

MethodDescription
generateFromUsage(customerId, plan, usage, { subscriptionId?, taxRate?, currency? })Build an invoice from a plan and a usage summary (line items via mapUsageToLineItems, tax, numbering)
createCustom(input)Ad-hoc invoice (CreateInvoiceInput)
markPaid(invoiceId, amount?) / void(invoiceId)State changes
get(invoiceId) / getByCustomer(customerId)Look-ups

createMemoryInvoiceStore() is the in-memory InvoiceStore (create, get, getByCustomer, update, getNextNumber).

FunctionSignatureDescription
getBillingMigrationsDir() => stringGet path to billing SQL migration files (auto-discovered by vk migrate)
TypeDescription
PlanDefinitionPlan with id, name, and feature map
PlanFeatureFeature definition with numeric limit or boolean
Subscription, SubscriptionStore, CreateSubscriptionInput, SubscriptionManagerOptions, ProrationResultSubscription types
UsageRecord, UsageStoreUsage types
FeatureGateOptions, FeatureCheckResultGating types
Entitlements, EntitlementsResolver, EntitlementsResolverOptions, EntitlementOverrides, ResolvedFeature, LimitCheck, EntitlementErrorCodeEntitlement types
Invoice, InvoiceStore, CreateInvoiceInput, InvoiceGeneratorOptionsInvoice types

Additional billing types are exported from ./types/index.js.