@trendyvideo/billing 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/billing-coverage.test.d.ts +2 -0
- package/dist/__tests__/billing-coverage.test.d.ts.map +1 -0
- package/dist/__tests__/billing-coverage.test.js +170 -0
- package/dist/__tests__/billing-coverage.test.js.map +1 -0
- package/dist/__tests__/plan-limits.test.d.ts +2 -0
- package/dist/__tests__/plan-limits.test.d.ts.map +1 -0
- package/dist/__tests__/plan-limits.test.js +139 -0
- package/dist/__tests__/plan-limits.test.js.map +1 -0
- package/dist/__tests__/pricing.test.d.ts +2 -0
- package/dist/__tests__/pricing.test.d.ts.map +1 -0
- package/dist/__tests__/pricing.test.js +146 -0
- package/dist/__tests__/pricing.test.js.map +1 -0
- package/dist/__tests__/usage-tracker.test.d.ts +2 -0
- package/dist/__tests__/usage-tracker.test.d.ts.map +1 -0
- package/dist/__tests__/usage-tracker.test.js +210 -0
- package/dist/__tests__/usage-tracker.test.js.map +1 -0
- package/dist/alerts.d.ts +19 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +90 -0
- package/dist/alerts.js.map +1 -0
- package/dist/guards/project-guard.d.ts +6 -0
- package/dist/guards/project-guard.d.ts.map +1 -0
- package/dist/guards/project-guard.js +56 -0
- package/dist/guards/project-guard.js.map +1 -0
- package/dist/guards/resolution-guard.d.ts +6 -0
- package/dist/guards/resolution-guard.d.ts.map +1 -0
- package/dist/guards/resolution-guard.js +40 -0
- package/dist/guards/resolution-guard.js.map +1 -0
- package/dist/guards/seat-guard.d.ts +8 -0
- package/dist/guards/seat-guard.d.ts.map +1 -0
- package/dist/guards/seat-guard.js +55 -0
- package/dist/guards/seat-guard.js.map +1 -0
- package/dist/guards/social-guard.d.ts +6 -0
- package/dist/guards/social-guard.d.ts.map +1 -0
- package/dist/guards/social-guard.js +52 -0
- package/dist/guards/social-guard.js.map +1 -0
- package/dist/guards/storage-guard.d.ts +14 -0
- package/dist/guards/storage-guard.d.ts.map +1 -0
- package/dist/guards/storage-guard.js +85 -0
- package/dist/guards/storage-guard.js.map +1 -0
- package/dist/guards/stream-guard.d.ts +7 -0
- package/dist/guards/stream-guard.d.ts.map +1 -0
- package/dist/guards/stream-guard.js +73 -0
- package/dist/guards/stream-guard.js.map +1 -0
- package/dist/guards/watch-guard.d.ts +13 -0
- package/dist/guards/watch-guard.d.ts.map +1 -0
- package/dist/guards/watch-guard.js +71 -0
- package/dist/guards/watch-guard.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/plan-limits.d.ts +25 -0
- package/dist/plan-limits.d.ts.map +1 -0
- package/dist/plan-limits.js +138 -0
- package/dist/plan-limits.js.map +1 -0
- package/dist/pricing.d.ts +32 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +126 -0
- package/dist/pricing.js.map +1 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-checker.d.ts +26 -0
- package/dist/usage-checker.d.ts.map +1 -0
- package/dist/usage-checker.js +295 -0
- package/dist/usage-checker.js.map +1 -0
- package/dist/usage-tracker.d.ts +25 -0
- package/dist/usage-tracker.d.ts.map +1 -0
- package/dist/usage-tracker.js +181 -0
- package/dist/usage-tracker.js.map +1 -0
- package/package.json +41 -0
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MICRODOLLARS_PER_CREDIT = exports.COST_MARKUP = exports.DEFAULT_PRICING = exports.MODEL_PRICING = void 0;
|
|
4
|
+
exports.getPricing = getPricing;
|
|
5
|
+
exports.calculateCostMicrodollars = calculateCostMicrodollars;
|
|
6
|
+
exports.microdollarsToCredits = microdollarsToCredits;
|
|
7
|
+
exports.creditsToMicrodollars = creditsToMicrodollars;
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Model Pricing — Single Source of Truth
|
|
10
|
+
// All prices in USD per 1 million tokens
|
|
11
|
+
// ============================================================================
|
|
12
|
+
exports.MODEL_PRICING = {
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Claude models
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
'claude-opus-4-6': { input: 5, output: 25, cacheRead: 0.50, cacheWrite: 6.25 },
|
|
17
|
+
'claude-opus-4-20250514': { input: 15, output: 75, cacheRead: 1.50, cacheWrite: 18.75 },
|
|
18
|
+
'claude-sonnet-4-6': { input: 3, output: 15, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
19
|
+
'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
20
|
+
'claude-sonnet-4-20250514': { input: 3, output: 15, cacheRead: 0.30, cacheWrite: 3.75 },
|
|
21
|
+
'claude-haiku-4-5-20251001': { input: 1, output: 5, cacheRead: 0.10, cacheWrite: 1.25 },
|
|
22
|
+
'claude-haiku-3-5-20241022': { input: 0.80, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Gemini models
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
'gemini-3.1-pro-preview': { input: 2.50, output: 15, cacheRead: 0, cacheWrite: 0 },
|
|
27
|
+
// Tool-call-optimized Pro variant — same rate card as vanilla 3.1 Pro preview.
|
|
28
|
+
'gemini-3.1-pro-preview-customtools': { input: 2.50, output: 15, cacheRead: 0, cacheWrite: 0 },
|
|
29
|
+
// gemini-3.1-flash-preview was a phantom SKU — never published on Vertex AI
|
|
30
|
+
// (returns 404). The actual Flash 3.x SKUs are:
|
|
31
|
+
// - `gemini-3-flash-preview` (regular Flash, global region)
|
|
32
|
+
// - `gemini-3.1-flash-lite-preview` (cheaper Lite variant, global region)
|
|
33
|
+
'gemini-3-flash-preview': { input: 0.30, output: 2.50, cacheRead: 0, cacheWrite: 0 },
|
|
34
|
+
'gemini-3.1-flash-lite-preview': { input: 0.15, output: 0.60, cacheRead: 0, cacheWrite: 0 },
|
|
35
|
+
'gemini-2.5-pro': { input: 1.25, output: 10, cacheRead: 0, cacheWrite: 0 },
|
|
36
|
+
'gemini-2.5-flash': { input: 0.15, output: 0.60, cacheRead: 0, cacheWrite: 0 },
|
|
37
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, cacheRead: 0, cacheWrite: 0 },
|
|
38
|
+
'gemini-1.5-pro': { input: 1.25, output: 5, cacheRead: 0, cacheWrite: 0 },
|
|
39
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30, cacheRead: 0, cacheWrite: 0 },
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Gemini image generation models (flat-rate — use costMicrodollarsOverride)
|
|
42
|
+
// Pricing: ~$0.04/image for Pro, ~$0.02/image for Flash
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
'gemini-3-pro-image-preview': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
45
|
+
'gemini-3.1-flash-image-preview': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Non-token services (flat-rate — use costMicrodollarsOverride when recording)
|
|
48
|
+
// Pricing listed here for reference; input = cost per 1M "units"
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
'render-engine': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
51
|
+
'chirp-tts': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
52
|
+
'veo-video-gen': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
53
|
+
'text-embedding-005': { input: 0.025, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Storage (flat-rate — $0.05/GB/month, use costMicrodollarsOverride)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
'storage': { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Default pricing used when the model is not found in MODEL_PRICING.
|
|
61
|
+
* Falls back to Claude Sonnet pricing as a safe default.
|
|
62
|
+
*/
|
|
63
|
+
exports.DEFAULT_PRICING = {
|
|
64
|
+
input: 3,
|
|
65
|
+
output: 15,
|
|
66
|
+
cacheRead: 0.30,
|
|
67
|
+
cacheWrite: 3.75,
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Look up pricing for a model. Tries exact match first, then prefix match
|
|
71
|
+
* (stripping date suffixes like `-20250514`), then falls back to DEFAULT_PRICING.
|
|
72
|
+
*/
|
|
73
|
+
function getPricing(model) {
|
|
74
|
+
// Exact match
|
|
75
|
+
if (exports.MODEL_PRICING[model])
|
|
76
|
+
return exports.MODEL_PRICING[model];
|
|
77
|
+
// Prefix match (e.g. "gemini-2.5-flash-preview-05-20" → "gemini-2.5-flash")
|
|
78
|
+
for (const [key, pricing] of Object.entries(exports.MODEL_PRICING)) {
|
|
79
|
+
if (model.startsWith(key))
|
|
80
|
+
return pricing;
|
|
81
|
+
}
|
|
82
|
+
// Strip trailing date suffix and retry (e.g. "claude-sonnet-4-6-20260101" → "claude-sonnet-4-6")
|
|
83
|
+
const stripped = model.replace(/-\d{8}$/, '');
|
|
84
|
+
if (stripped !== model && exports.MODEL_PRICING[stripped]) {
|
|
85
|
+
return exports.MODEL_PRICING[stripped];
|
|
86
|
+
}
|
|
87
|
+
return exports.DEFAULT_PRICING;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Markup applied to all API costs to ensure profit margin.
|
|
91
|
+
* 1.5x means we charge 50% more than raw cost → ~33% margin.
|
|
92
|
+
* Applied to both token-based costs AND flat-rate overrides.
|
|
93
|
+
*/
|
|
94
|
+
exports.COST_MARKUP = 1.5;
|
|
95
|
+
/**
|
|
96
|
+
* Calculate cost in microdollars (1 microdollar = $0.000001).
|
|
97
|
+
*
|
|
98
|
+
* Since pricing is per-million tokens:
|
|
99
|
+
* cost_usd = (tokens / 1_000_000) * rate_per_million
|
|
100
|
+
* cost_microdollars = cost_usd * 1_000_000 = tokens * rate_per_million
|
|
101
|
+
*
|
|
102
|
+
* All costs include COST_MARKUP (1.5x) for profit margin.
|
|
103
|
+
*/
|
|
104
|
+
function calculateCostMicrodollars(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheCreationTokens = 0, costMicrodollarsOverride) {
|
|
105
|
+
if (costMicrodollarsOverride !== undefined) {
|
|
106
|
+
return Math.round(costMicrodollarsOverride * exports.COST_MARKUP);
|
|
107
|
+
}
|
|
108
|
+
const p = getPricing(model);
|
|
109
|
+
const inputCost = inputTokens * p.input;
|
|
110
|
+
const outputCost = outputTokens * p.output;
|
|
111
|
+
const cacheReadCost = cacheReadTokens * p.cacheRead;
|
|
112
|
+
const cacheWriteCost = cacheCreationTokens * p.cacheWrite;
|
|
113
|
+
const rawCost = inputCost + outputCost + cacheReadCost + cacheWriteCost;
|
|
114
|
+
return Math.round(rawCost * exports.COST_MARKUP);
|
|
115
|
+
}
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Credit System — 1 credit = $0.01 = 10,000 microdollars
|
|
118
|
+
// ============================================================================
|
|
119
|
+
exports.MICRODOLLARS_PER_CREDIT = 10_000;
|
|
120
|
+
function microdollarsToCredits(microdollars) {
|
|
121
|
+
return Math.ceil(microdollars / exports.MICRODOLLARS_PER_CREDIT);
|
|
122
|
+
}
|
|
123
|
+
function creditsToMicrodollars(credits) {
|
|
124
|
+
return credits * exports.MICRODOLLARS_PER_CREDIT;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=pricing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Supabase client interface — accepts any version of @supabase/supabase-js
|
|
3
|
+
* without requiring exact type identity (avoids duplicate package type mismatches)
|
|
4
|
+
*/
|
|
5
|
+
export interface SupabaseClientLike {
|
|
6
|
+
rpc(fn: string, params?: Record<string, unknown>): PromiseLike<{
|
|
7
|
+
data: unknown;
|
|
8
|
+
error: {
|
|
9
|
+
message: string;
|
|
10
|
+
} | null;
|
|
11
|
+
}>;
|
|
12
|
+
from(table: string): any;
|
|
13
|
+
}
|
|
14
|
+
export interface ModelPricing {
|
|
15
|
+
/** Cost per 1M input tokens (USD) */
|
|
16
|
+
input: number;
|
|
17
|
+
/** Cost per 1M output tokens (USD) */
|
|
18
|
+
output: number;
|
|
19
|
+
/** Cost per 1M cached read tokens (USD). 0 if not applicable. */
|
|
20
|
+
cacheRead: number;
|
|
21
|
+
/** Cost per 1M cache creation tokens (USD). 0 if not applicable. */
|
|
22
|
+
cacheWrite: number;
|
|
23
|
+
}
|
|
24
|
+
export type PlanName = 'free' | 'creator' | 'pro' | 'agency';
|
|
25
|
+
export interface PlanLimits {
|
|
26
|
+
/** Storage quota in bytes */
|
|
27
|
+
storageBytes: number;
|
|
28
|
+
/** Max renders per billing period */
|
|
29
|
+
rendersPerMonth: number;
|
|
30
|
+
/** Max concurrent streams */
|
|
31
|
+
streams: number;
|
|
32
|
+
/** Max projects */
|
|
33
|
+
projects: number;
|
|
34
|
+
/** Max connected social accounts */
|
|
35
|
+
socialAccounts: number;
|
|
36
|
+
/** Max export resolution */
|
|
37
|
+
maxResolution: '720p' | '1080p' | '4k';
|
|
38
|
+
/** Number of base team seats included in the plan */
|
|
39
|
+
seats: number;
|
|
40
|
+
/** Price per additional seat in cents (0 = no seat add-ons allowed) */
|
|
41
|
+
seatPrice: number;
|
|
42
|
+
/** Credits granted per additional seat */
|
|
43
|
+
creditsPerSeat: number;
|
|
44
|
+
/** Whether exports include a watermark */
|
|
45
|
+
watermark: boolean;
|
|
46
|
+
/** Max streamer watches */
|
|
47
|
+
maxWatches: number;
|
|
48
|
+
/** Included credits per billing period (1 credit = $0.01) */
|
|
49
|
+
includedCredits: number;
|
|
50
|
+
/** Max overage credits before hard block */
|
|
51
|
+
overageCapCredits: number;
|
|
52
|
+
}
|
|
53
|
+
export interface UsageEvent {
|
|
54
|
+
organizationId: string;
|
|
55
|
+
/** Service name (e.g. 'agent', 'chunk-analyzer', 'reviral') */
|
|
56
|
+
service: string;
|
|
57
|
+
/** Model identifier (e.g. 'claude-sonnet-4-6', 'gemini-2.5-pro') */
|
|
58
|
+
model: string;
|
|
59
|
+
inputTokens: number;
|
|
60
|
+
outputTokens: number;
|
|
61
|
+
cacheReadTokens?: number;
|
|
62
|
+
cacheCreationTokens?: number;
|
|
63
|
+
apiCalls?: number;
|
|
64
|
+
/** Override auto-calculated cost for flat-rate services (Chirp TTS, Veo, etc.) */
|
|
65
|
+
costMicrodollarsOverride?: number;
|
|
66
|
+
/** Credits consumed by this event (auto-computed from costMicrodollars) */
|
|
67
|
+
creditsConsumed?: number;
|
|
68
|
+
/** Optional conversation ID for agent usage */
|
|
69
|
+
conversationId?: string;
|
|
70
|
+
/** Optional context (stream_id, chunk_number, project_id, etc.) */
|
|
71
|
+
metadata?: Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* True when the platform eats this cost (conversation naming, compaction,
|
|
74
|
+
* summaries, project auto-naming, memories, studio suggestions, etc.).
|
|
75
|
+
* Recorded to usage_events for analytics, but skipped from the usage_records
|
|
76
|
+
* upsert so it never hits the user's credit/subscription balance.
|
|
77
|
+
* Metadata is auto-augmented with { platform_paid: true } in recordUsage.
|
|
78
|
+
*/
|
|
79
|
+
platformPaid?: boolean;
|
|
80
|
+
}
|
|
81
|
+
export interface UsageRecord {
|
|
82
|
+
organizationId: string;
|
|
83
|
+
periodStart: string;
|
|
84
|
+
periodEnd: string;
|
|
85
|
+
inputTokens: number;
|
|
86
|
+
outputTokens: number;
|
|
87
|
+
cacheReadTokens: number;
|
|
88
|
+
cacheCreationTokens: number;
|
|
89
|
+
apiCallCount: number;
|
|
90
|
+
renderCount: number;
|
|
91
|
+
/** Total credits used in this billing period */
|
|
92
|
+
creditsUsed?: number;
|
|
93
|
+
}
|
|
94
|
+
export interface UsageCheckResult {
|
|
95
|
+
allowed: boolean;
|
|
96
|
+
reason?: 'no_subscription' | 'subscription_inactive' | 'free_limit_exceeded' | 'overage_cap_reached';
|
|
97
|
+
warning?: 'approaching_limit' | 'in_overage';
|
|
98
|
+
usage: {
|
|
99
|
+
/** Credits used in the current billing period */
|
|
100
|
+
creditsUsed: number;
|
|
101
|
+
/** Credits included in the plan */
|
|
102
|
+
creditsIncluded: number;
|
|
103
|
+
/** Remaining credits (can be negative if in overage) */
|
|
104
|
+
creditsRemaining: number;
|
|
105
|
+
/** Percentage of included credits consumed */
|
|
106
|
+
creditPercentUsed: number;
|
|
107
|
+
/** Whether the organization has exceeded included credits */
|
|
108
|
+
isOverCredits: boolean;
|
|
109
|
+
/** Number of overage credits consumed beyond the included amount */
|
|
110
|
+
overageCredits: number;
|
|
111
|
+
totalTokens?: number;
|
|
112
|
+
includedLimit?: number;
|
|
113
|
+
overageTokens?: number;
|
|
114
|
+
percentUsed?: number;
|
|
115
|
+
inputTokens?: number;
|
|
116
|
+
outputTokens?: number;
|
|
117
|
+
includedInputLimit?: number;
|
|
118
|
+
includedOutputLimit?: number;
|
|
119
|
+
inputOverageTokens?: number;
|
|
120
|
+
outputOverageTokens?: number;
|
|
121
|
+
inputOverageCost?: number;
|
|
122
|
+
outputOverageCost?: number;
|
|
123
|
+
totalOverageCost?: number;
|
|
124
|
+
overageCap?: number;
|
|
125
|
+
};
|
|
126
|
+
plan: {
|
|
127
|
+
id: string;
|
|
128
|
+
name: string;
|
|
129
|
+
inputOverageRatePer1k?: number;
|
|
130
|
+
outputOverageRatePer1k?: number;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export interface RenderLimitResult {
|
|
134
|
+
allowed: boolean;
|
|
135
|
+
rendersUsed: number;
|
|
136
|
+
maxRenders: number;
|
|
137
|
+
message?: string;
|
|
138
|
+
}
|
|
139
|
+
export interface StorageQuotaResult {
|
|
140
|
+
allowed: boolean;
|
|
141
|
+
currentUsageBytes: number;
|
|
142
|
+
quotaBytes: number;
|
|
143
|
+
planName: string;
|
|
144
|
+
reason?: string;
|
|
145
|
+
}
|
|
146
|
+
export interface StreamLimitResult {
|
|
147
|
+
allowed: boolean;
|
|
148
|
+
activeStreams: number;
|
|
149
|
+
maxStreams: number;
|
|
150
|
+
reason?: string;
|
|
151
|
+
}
|
|
152
|
+
export interface ProjectLimitResult {
|
|
153
|
+
allowed: boolean;
|
|
154
|
+
currentProjects: number;
|
|
155
|
+
maxProjects: number;
|
|
156
|
+
reason?: string;
|
|
157
|
+
}
|
|
158
|
+
export interface SocialAccountLimitResult {
|
|
159
|
+
allowed: boolean;
|
|
160
|
+
currentAccounts: number;
|
|
161
|
+
maxAccounts: number;
|
|
162
|
+
reason?: string;
|
|
163
|
+
}
|
|
164
|
+
export interface ResolutionLimitResult {
|
|
165
|
+
allowed: boolean;
|
|
166
|
+
requestedResolution: string;
|
|
167
|
+
maxResolution: string;
|
|
168
|
+
reason?: string;
|
|
169
|
+
}
|
|
170
|
+
export interface SeatLimitResult {
|
|
171
|
+
allowed: boolean;
|
|
172
|
+
currentSeats: number;
|
|
173
|
+
maxSeats: number;
|
|
174
|
+
reason?: string;
|
|
175
|
+
}
|
|
176
|
+
export interface UsageAlertResult {
|
|
177
|
+
shouldSend: boolean;
|
|
178
|
+
percentUsed: number;
|
|
179
|
+
alreadySent: boolean;
|
|
180
|
+
}
|
|
181
|
+
export interface BillingLogger {
|
|
182
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
183
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
184
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
185
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
186
|
+
}
|
|
187
|
+
export type SupabaseClient = SupabaseClientLike;
|
|
188
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SupabaseClient, UsageCheckResult, RenderLimitResult, BillingLogger } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether an organization is allowed to continue using AI services.
|
|
4
|
+
* Credits are the SOLE enforcement mechanism.
|
|
5
|
+
*
|
|
6
|
+
* Logic:
|
|
7
|
+
* - Free plan: hard block when credits exhausted (no overages).
|
|
8
|
+
* - Paid plans: allow overages up to the plan's overage credit cap.
|
|
9
|
+
*
|
|
10
|
+
* Token counts are still returned for analytics/logging but are NOT
|
|
11
|
+
* used for any enforcement decisions.
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkUsage(supabase: SupabaseClient, orgId: string, logger?: BillingLogger): Promise<UsageCheckResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Check if an organization has remaining renders in the current billing period.
|
|
16
|
+
* Fails CLOSED on errors — if we cannot verify the render quota, the render
|
|
17
|
+
* is denied. Includes 1 retry with a short delay.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkRenderLimit(supabase: SupabaseClient, orgId: string, logger?: BillingLogger): Promise<RenderLimitResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Increment the render count for an organization in the current billing period.
|
|
22
|
+
* Should be called after a render job completes successfully.
|
|
23
|
+
* Never throws.
|
|
24
|
+
*/
|
|
25
|
+
export declare function incrementRenders(supabase: SupabaseClient, orgId: string, count?: number, logger?: BillingLogger): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=usage-checker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-checker.d.ts","sourceRoot":"","sources":["../src/usage-checker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAiF7F;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA8G3B;AASD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CAsE5B;AAMD;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,SAAI,EACT,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkUsage = checkUsage;
|
|
4
|
+
exports.checkRenderLimit = checkRenderLimit;
|
|
5
|
+
exports.incrementRenders = incrementRenders;
|
|
6
|
+
const pricing_1 = require("./pricing");
|
|
7
|
+
const plan_limits_1 = require("./plan-limits");
|
|
8
|
+
const defaultLogger = {
|
|
9
|
+
debug: () => { },
|
|
10
|
+
info: (msg, meta) => console.log(`[billing] ${msg}`, meta ?? ''),
|
|
11
|
+
warn: (msg, meta) => console.warn(`[billing] ${msg}`, meta ?? ''),
|
|
12
|
+
error: (msg, meta) => console.error(`[billing] ${msg}`, meta ?? ''),
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Log an enforcement/audit event to the billing_events table.
|
|
16
|
+
* Fire-and-forget — never throws or blocks the caller.
|
|
17
|
+
*/
|
|
18
|
+
async function logBillingEvent(supabase, orgId, eventType, description, details, logger) {
|
|
19
|
+
try {
|
|
20
|
+
const { error } = await supabase.from('billing_events').insert({
|
|
21
|
+
organization_id: orgId,
|
|
22
|
+
event_type: eventType,
|
|
23
|
+
description,
|
|
24
|
+
amount_cents: 0,
|
|
25
|
+
related_data: details,
|
|
26
|
+
});
|
|
27
|
+
if (error) {
|
|
28
|
+
logger.warn('Failed to log billing event', {
|
|
29
|
+
orgId,
|
|
30
|
+
eventType,
|
|
31
|
+
error: error.message,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Never block the caller
|
|
37
|
+
logger.warn('Unexpected error logging billing event', {
|
|
38
|
+
orgId,
|
|
39
|
+
eventType,
|
|
40
|
+
error: err instanceof Error ? err.message : String(err),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Empty / default values
|
|
46
|
+
// ============================================================================
|
|
47
|
+
const EMPTY_USAGE = {
|
|
48
|
+
creditsUsed: 0,
|
|
49
|
+
creditsIncluded: 0,
|
|
50
|
+
creditsRemaining: 0,
|
|
51
|
+
creditPercentUsed: 0,
|
|
52
|
+
isOverCredits: false,
|
|
53
|
+
overageCredits: 0,
|
|
54
|
+
};
|
|
55
|
+
const UNKNOWN_PLAN = {
|
|
56
|
+
id: 'unknown',
|
|
57
|
+
name: 'Unknown',
|
|
58
|
+
};
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// checkUsage
|
|
61
|
+
// ============================================================================
|
|
62
|
+
/**
|
|
63
|
+
* Check whether an organization is allowed to continue using AI services.
|
|
64
|
+
* Credits are the SOLE enforcement mechanism.
|
|
65
|
+
*
|
|
66
|
+
* Logic:
|
|
67
|
+
* - Free plan: hard block when credits exhausted (no overages).
|
|
68
|
+
* - Paid plans: allow overages up to the plan's overage credit cap.
|
|
69
|
+
*
|
|
70
|
+
* Token counts are still returned for analytics/logging but are NOT
|
|
71
|
+
* used for any enforcement decisions.
|
|
72
|
+
*/
|
|
73
|
+
async function checkUsage(supabase, orgId, logger = defaultLogger) {
|
|
74
|
+
const { data, error } = await supabase.rpc('get_org_current_usage', { org_id: orgId });
|
|
75
|
+
if (error) {
|
|
76
|
+
logger.error('Failed to check org usage', { orgId, error: error.message });
|
|
77
|
+
return { allowed: false, reason: 'no_subscription', usage: EMPTY_USAGE, plan: UNKNOWN_PLAN };
|
|
78
|
+
}
|
|
79
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
80
|
+
if (!row) {
|
|
81
|
+
return {
|
|
82
|
+
allowed: false,
|
|
83
|
+
reason: 'no_subscription',
|
|
84
|
+
usage: EMPTY_USAGE,
|
|
85
|
+
plan: { ...UNKNOWN_PLAN, id: 'none', name: 'None' },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const planId = row.plan_id ?? 'unknown';
|
|
89
|
+
const planName = row.plan_name ?? 'Unknown';
|
|
90
|
+
// Credit calculations — sole enforcement mechanism
|
|
91
|
+
const costMicrodollars = row.cost_microdollars ?? 0;
|
|
92
|
+
const creditsUsed = (0, pricing_1.microdollarsToCredits)(costMicrodollars);
|
|
93
|
+
const planLimits = (0, plan_limits_1.getPlanLimits)((0, plan_limits_1.normalizePlanName)(planName));
|
|
94
|
+
// Include bonus credits from additional purchased seats
|
|
95
|
+
const additionalSeats = row.additional_seats ?? 0;
|
|
96
|
+
const seatBonusCredits = additionalSeats * planLimits.creditsPerSeat;
|
|
97
|
+
const creditsIncluded = planLimits.includedCredits + seatBonusCredits;
|
|
98
|
+
const creditsRemaining = creditsIncluded - creditsUsed;
|
|
99
|
+
const creditPercentUsed = creditsIncluded > 0 ? (creditsUsed / creditsIncluded) * 100 : 0;
|
|
100
|
+
const isOverCredits = creditsUsed > creditsIncluded;
|
|
101
|
+
const overageCredits = Math.max(0, creditsUsed - creditsIncluded);
|
|
102
|
+
// Raw token counts (analytics only — NOT used for enforcement)
|
|
103
|
+
const inputTokens = row.used_input_tokens ?? 0;
|
|
104
|
+
const outputTokens = row.used_output_tokens ?? 0;
|
|
105
|
+
const totalTokens = inputTokens + outputTokens;
|
|
106
|
+
const usage = {
|
|
107
|
+
creditsUsed,
|
|
108
|
+
creditsIncluded,
|
|
109
|
+
creditsRemaining,
|
|
110
|
+
creditPercentUsed,
|
|
111
|
+
isOverCredits,
|
|
112
|
+
overageCredits,
|
|
113
|
+
// Analytics-only token data
|
|
114
|
+
totalTokens,
|
|
115
|
+
inputTokens,
|
|
116
|
+
outputTokens,
|
|
117
|
+
};
|
|
118
|
+
const plan = {
|
|
119
|
+
id: planId,
|
|
120
|
+
name: planName,
|
|
121
|
+
};
|
|
122
|
+
// Free plan hard limit — block when credits exhausted
|
|
123
|
+
if (planId === 'free' && isOverCredits) {
|
|
124
|
+
logBillingEvent(supabase, orgId, 'limit_exceeded', 'Free plan credit limit exceeded', {
|
|
125
|
+
planId,
|
|
126
|
+
creditsUsed,
|
|
127
|
+
creditsIncluded,
|
|
128
|
+
creditPercentUsed,
|
|
129
|
+
}, logger);
|
|
130
|
+
return { allowed: false, reason: 'free_limit_exceeded', usage, plan };
|
|
131
|
+
}
|
|
132
|
+
// Paid plans — block when overage credits reach the cap
|
|
133
|
+
const overageCapCredits = planLimits.overageCapCredits;
|
|
134
|
+
if (planId !== 'free' && overageCredits >= overageCapCredits && overageCapCredits > 0) {
|
|
135
|
+
logBillingEvent(supabase, orgId, 'overage_cap_reached', 'Monthly overage credit cap reached', {
|
|
136
|
+
planId,
|
|
137
|
+
creditsUsed,
|
|
138
|
+
creditsIncluded,
|
|
139
|
+
overageCredits,
|
|
140
|
+
overageCapCredits,
|
|
141
|
+
}, logger);
|
|
142
|
+
return { allowed: false, reason: 'overage_cap_reached', usage, plan };
|
|
143
|
+
}
|
|
144
|
+
// Warnings (credit-based only)
|
|
145
|
+
let warning;
|
|
146
|
+
if (isOverCredits) {
|
|
147
|
+
warning = 'in_overage';
|
|
148
|
+
// Log overage start only when first entering overage (within 5% band to reduce noise)
|
|
149
|
+
if (creditPercentUsed <= 105) {
|
|
150
|
+
logBillingEvent(supabase, orgId, 'usage_warning', 'Organization entered credit overage', {
|
|
151
|
+
planId,
|
|
152
|
+
creditsUsed,
|
|
153
|
+
creditsIncluded,
|
|
154
|
+
overageCredits,
|
|
155
|
+
warning: 'in_overage',
|
|
156
|
+
}, logger);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (creditPercentUsed > 80) {
|
|
160
|
+
warning = 'approaching_limit';
|
|
161
|
+
// Log warning when crossing 80% threshold (within 2% band to reduce noise)
|
|
162
|
+
if (creditPercentUsed <= 82) {
|
|
163
|
+
logBillingEvent(supabase, orgId, 'usage_warning', 'Credit usage approaching plan limit (80%)', {
|
|
164
|
+
planId,
|
|
165
|
+
creditsUsed,
|
|
166
|
+
creditsIncluded,
|
|
167
|
+
creditPercentUsed,
|
|
168
|
+
warning: 'approaching_limit',
|
|
169
|
+
}, logger);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { allowed: true, warning, usage, plan };
|
|
173
|
+
}
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// checkRenderLimit
|
|
176
|
+
// ============================================================================
|
|
177
|
+
const RENDER_RETRY_DELAY_MS = 500;
|
|
178
|
+
const RENDER_MAX_ATTEMPTS = 2;
|
|
179
|
+
/**
|
|
180
|
+
* Check if an organization has remaining renders in the current billing period.
|
|
181
|
+
* Fails CLOSED on errors — if we cannot verify the render quota, the render
|
|
182
|
+
* is denied. Includes 1 retry with a short delay.
|
|
183
|
+
*/
|
|
184
|
+
async function checkRenderLimit(supabase, orgId, logger = defaultLogger) {
|
|
185
|
+
const failClosed = {
|
|
186
|
+
allowed: false,
|
|
187
|
+
rendersUsed: 0,
|
|
188
|
+
maxRenders: 0,
|
|
189
|
+
message: 'Unable to verify render quota. Please try again.',
|
|
190
|
+
};
|
|
191
|
+
for (let attempt = 1; attempt <= RENDER_MAX_ATTEMPTS; attempt++) {
|
|
192
|
+
try {
|
|
193
|
+
const { data, error } = await supabase.rpc('check_render_limit', {
|
|
194
|
+
p_organization_id: orgId,
|
|
195
|
+
});
|
|
196
|
+
if (error) {
|
|
197
|
+
logger.error('Failed to check render limit', {
|
|
198
|
+
orgId,
|
|
199
|
+
error: error.message,
|
|
200
|
+
attempt,
|
|
201
|
+
});
|
|
202
|
+
if (attempt < RENDER_MAX_ATTEMPTS) {
|
|
203
|
+
await new Promise((r) => setTimeout(r, RENDER_RETRY_DELAY_MS));
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
return failClosed;
|
|
207
|
+
}
|
|
208
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
209
|
+
if (!row) {
|
|
210
|
+
return {
|
|
211
|
+
allowed: false,
|
|
212
|
+
rendersUsed: 0,
|
|
213
|
+
maxRenders: 0,
|
|
214
|
+
message: 'No active subscription found. Please check your billing settings.',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const rendersUsed = row.renders_used ?? 0;
|
|
218
|
+
const maxRenders = row.max_renders ?? 2147483647;
|
|
219
|
+
if (rendersUsed >= maxRenders) {
|
|
220
|
+
logBillingEvent(supabase, orgId, 'render_limit_reached', 'Monthly render limit reached', {
|
|
221
|
+
rendersUsed,
|
|
222
|
+
maxRenders,
|
|
223
|
+
}, logger);
|
|
224
|
+
return {
|
|
225
|
+
allowed: false,
|
|
226
|
+
rendersUsed,
|
|
227
|
+
maxRenders,
|
|
228
|
+
message: `Render limit reached: ${rendersUsed}/${maxRenders} used. Upgrade your plan for more renders.`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return { allowed: true, rendersUsed, maxRenders };
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
logger.error('Unexpected error checking render limit', {
|
|
235
|
+
orgId,
|
|
236
|
+
error: err instanceof Error ? err.message : String(err),
|
|
237
|
+
attempt,
|
|
238
|
+
});
|
|
239
|
+
if (attempt < RENDER_MAX_ATTEMPTS) {
|
|
240
|
+
await new Promise((r) => setTimeout(r, RENDER_RETRY_DELAY_MS));
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return failClosed;
|
|
246
|
+
}
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// incrementRenders
|
|
249
|
+
// ============================================================================
|
|
250
|
+
/**
|
|
251
|
+
* Increment the render count for an organization in the current billing period.
|
|
252
|
+
* Should be called after a render job completes successfully.
|
|
253
|
+
* Never throws.
|
|
254
|
+
*/
|
|
255
|
+
async function incrementRenders(supabase, orgId, count = 1, logger = defaultLogger) {
|
|
256
|
+
try {
|
|
257
|
+
// Get the current billing period for the RPC
|
|
258
|
+
const { data: sub, error: subError } = await supabase
|
|
259
|
+
.from('organization_subscriptions')
|
|
260
|
+
.select('current_period_start, current_period_end')
|
|
261
|
+
.eq('organization_id', orgId)
|
|
262
|
+
.order('created_at', { ascending: false })
|
|
263
|
+
.limit(1)
|
|
264
|
+
.single();
|
|
265
|
+
if (subError || !sub) {
|
|
266
|
+
logger.warn('No subscription found for render count increment', {
|
|
267
|
+
orgId,
|
|
268
|
+
error: subError?.message,
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Call the correct RPC: increment_renders_used (matches DB migration)
|
|
273
|
+
for (let i = 0; i < count; i++) {
|
|
274
|
+
const { error } = await supabase.rpc('increment_renders_used', {
|
|
275
|
+
p_organization_id: orgId,
|
|
276
|
+
p_period_start: sub.current_period_start,
|
|
277
|
+
p_period_end: sub.current_period_end,
|
|
278
|
+
});
|
|
279
|
+
if (error) {
|
|
280
|
+
logger.error('RPC increment_renders_used failed', {
|
|
281
|
+
orgId,
|
|
282
|
+
error: error.message,
|
|
283
|
+
iteration: i + 1,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
logger.error('Failed to increment render count', {
|
|
290
|
+
orgId,
|
|
291
|
+
error: err instanceof Error ? err.message : String(err),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=usage-checker.js.map
|