@soulcraft/sdk 1.0.0 → 1.2.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/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/auth/service-token.d.ts +62 -0
- package/dist/modules/auth/service-token.d.ts.map +1 -0
- package/dist/modules/auth/service-token.js +99 -0
- package/dist/modules/auth/service-token.js.map +1 -0
- package/dist/modules/billing/firestore-provider.d.ts +60 -0
- package/dist/modules/billing/firestore-provider.d.ts.map +1 -0
- package/dist/modules/billing/firestore-provider.js +314 -0
- package/dist/modules/billing/firestore-provider.js.map +1 -0
- package/dist/modules/billing/index.d.ts +58 -0
- package/dist/modules/billing/index.d.ts.map +1 -0
- package/dist/modules/billing/index.js +164 -0
- package/dist/modules/billing/index.js.map +1 -0
- package/dist/modules/billing/local-provider.d.ts +38 -0
- package/dist/modules/billing/local-provider.d.ts.map +1 -0
- package/dist/modules/billing/local-provider.js +242 -0
- package/dist/modules/billing/local-provider.js.map +1 -0
- package/dist/modules/billing/portal-provider.d.ts +70 -0
- package/dist/modules/billing/portal-provider.d.ts.map +1 -0
- package/dist/modules/billing/portal-provider.js +204 -0
- package/dist/modules/billing/portal-provider.js.map +1 -0
- package/dist/modules/billing/types.d.ts +323 -3
- package/dist/modules/billing/types.d.ts.map +1 -1
- package/dist/modules/billing/types.js +22 -2
- package/dist/modules/billing/types.js.map +1 -1
- package/dist/modules/billing/usage-buffer.d.ts +72 -0
- package/dist/modules/billing/usage-buffer.d.ts.map +1 -0
- package/dist/modules/billing/usage-buffer.js +141 -0
- package/dist/modules/billing/usage-buffer.js.map +1 -0
- package/dist/modules/formats/types.d.ts +65 -3
- package/dist/modules/formats/types.d.ts.map +1 -1
- package/dist/modules/formats/types.js +40 -3
- package/dist/modules/formats/types.js.map +1 -1
- package/dist/modules/formats/wdoc.d.ts +263 -0
- package/dist/modules/formats/wdoc.d.ts.map +1 -0
- package/dist/modules/formats/wdoc.js +21 -0
- package/dist/modules/formats/wdoc.js.map +1 -0
- package/dist/modules/formats/wquiz.d.ts +122 -0
- package/dist/modules/formats/wquiz.d.ts.map +1 -0
- package/dist/modules/formats/wquiz.js +23 -0
- package/dist/modules/formats/wquiz.js.map +1 -0
- package/dist/modules/formats/wslide.d.ts +130 -0
- package/dist/modules/formats/wslide.d.ts.map +1 -0
- package/dist/modules/formats/wslide.js +23 -0
- package/dist/modules/formats/wslide.js.map +1 -0
- package/dist/modules/formats/wviz.d.ts +114 -0
- package/dist/modules/formats/wviz.d.ts.map +1 -0
- package/dist/modules/formats/wviz.js +21 -0
- package/dist/modules/formats/wviz.js.map +1 -0
- package/dist/modules/hall/browser.d.ts +88 -0
- package/dist/modules/hall/browser.d.ts.map +1 -0
- package/dist/modules/hall/browser.js +265 -0
- package/dist/modules/hall/browser.js.map +1 -0
- package/dist/modules/hall/protocol.d.ts +39 -0
- package/dist/modules/hall/protocol.d.ts.map +1 -0
- package/dist/modules/hall/protocol.js +52 -0
- package/dist/modules/hall/protocol.js.map +1 -0
- package/dist/modules/hall/server.d.ts +172 -0
- package/dist/modules/hall/server.d.ts.map +1 -0
- package/dist/modules/hall/server.js +457 -0
- package/dist/modules/hall/server.js.map +1 -0
- package/dist/modules/hall/types.d.ts +502 -31
- package/dist/modules/hall/types.d.ts.map +1 -1
- package/dist/modules/hall/types.js +13 -8
- package/dist/modules/hall/types.js.map +1 -1
- package/dist/modules/kits/index.d.ts +41 -0
- package/dist/modules/kits/index.d.ts.map +1 -0
- package/dist/modules/kits/index.js +85 -0
- package/dist/modules/kits/index.js.map +1 -0
- package/dist/modules/kits/types.d.ts +107 -3
- package/dist/modules/kits/types.d.ts.map +1 -1
- package/dist/modules/kits/types.js +15 -2
- package/dist/modules/kits/types.js.map +1 -1
- package/dist/modules/license/index.d.ts +53 -0
- package/dist/modules/license/index.d.ts.map +1 -0
- package/dist/modules/license/index.js +233 -0
- package/dist/modules/license/index.js.map +1 -0
- package/dist/modules/license/types.d.ts +222 -3
- package/dist/modules/license/types.d.ts.map +1 -1
- package/dist/modules/license/types.js +21 -2
- package/dist/modules/license/types.js.map +1 -1
- package/dist/modules/notifications/index.d.ts +40 -0
- package/dist/modules/notifications/index.d.ts.map +1 -0
- package/dist/modules/notifications/index.js +280 -0
- package/dist/modules/notifications/index.js.map +1 -0
- package/dist/modules/notifications/types.d.ts +152 -3
- package/dist/modules/notifications/types.d.ts.map +1 -1
- package/dist/modules/notifications/types.js +21 -2
- package/dist/modules/notifications/types.js.map +1 -1
- package/dist/server/create-sdk.d.ts +4 -0
- package/dist/server/create-sdk.d.ts.map +1 -1
- package/dist/server/create-sdk.js +19 -26
- package/dist/server/create-sdk.js.map +1 -1
- package/dist/server/hall-handlers.d.ts +90 -151
- package/dist/server/hall-handlers.d.ts.map +1 -1
- package/dist/server/hall-handlers.js +84 -204
- package/dist/server/hall-handlers.js.map +1 -1
- package/dist/server/index.d.ts +10 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -2
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +35 -25
- package/dist/types.d.ts.map +1 -1
- package/docs/USAGE.md +224 -1
- package/package.json +13 -5
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module billing/portal-provider
|
|
3
|
+
* @description Portal-backed billing provider for unified cross-product credit tracking.
|
|
4
|
+
*
|
|
5
|
+
* Delegates all credit checks and usage recording to the Portal credit API at
|
|
6
|
+
* `https://portal.soulcraft.com/api/credits/*`. This ensures that a user's
|
|
7
|
+
* 1,000 monthly credits are shared across Workshop, Venue, and Academy — not siloed
|
|
8
|
+
* per product.
|
|
9
|
+
*
|
|
10
|
+
* Activated when `PORTAL_CREDIT_SECRET` is set in the environment. Takes precedence
|
|
11
|
+
* over the Firestore provider.
|
|
12
|
+
*
|
|
13
|
+
* **Endpoint summary:**
|
|
14
|
+
* ```
|
|
15
|
+
* POST /api/credits/check — pre-flight before an AI call
|
|
16
|
+
* POST /api/credits/consume — deduct usage after a single AI response
|
|
17
|
+
* POST /api/credits/batch — flush buffered usage at end of session
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* All requests carry `X-Portal-Credit-Secret: <PORTAL_CREDIT_SECRET>`.
|
|
21
|
+
*
|
|
22
|
+
* Subscription data, top-up balances, and usage history are not surfaced via
|
|
23
|
+
* this API — those are managed by Portal directly. Methods that require them
|
|
24
|
+
* (`getUsageStatus`, `getSubscription`, `addTopUp`) return safe defaults.
|
|
25
|
+
* Products that need rich billing UI should call Portal's management API directly.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Automatically selected when PORTAL_CREDIT_SECRET is set:
|
|
30
|
+
* const billing = createBillingModule()
|
|
31
|
+
*
|
|
32
|
+
* // Explicit override (e.g. for testing):
|
|
33
|
+
* const billing = createBillingModule({
|
|
34
|
+
* provider: new PortalBillingProvider('https://portal.soulcraft.com', secret, 'workshop'),
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Period helpers
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
function _currentPeriod() {
|
|
42
|
+
const d = new Date();
|
|
43
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
44
|
+
}
|
|
45
|
+
function _thirtyDaysFromNow() {
|
|
46
|
+
return new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
47
|
+
}
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
// Provider
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Portal-backed billing provider.
|
|
53
|
+
*
|
|
54
|
+
* Calls the Portal credit API for all gate checks and usage recording.
|
|
55
|
+
* All requests are authenticated with the `X-Portal-Credit-Secret` header.
|
|
56
|
+
*
|
|
57
|
+
* The `userId` parameter on all methods must be the user's **email address** —
|
|
58
|
+
* the same identifier Portal uses to look up the license document.
|
|
59
|
+
*/
|
|
60
|
+
export class PortalBillingProvider {
|
|
61
|
+
baseUrl;
|
|
62
|
+
secret;
|
|
63
|
+
product;
|
|
64
|
+
/**
|
|
65
|
+
* @param baseUrl - Portal base URL (e.g. `"https://portal.soulcraft.com"`).
|
|
66
|
+
* @param secret - The value of `PORTAL_CREDIT_SECRET`.
|
|
67
|
+
* @param product - The product name sent with consume/batch calls (`'workshop'`, `'venue'`, `'academy'`).
|
|
68
|
+
*/
|
|
69
|
+
constructor(baseUrl, secret, product) {
|
|
70
|
+
this.baseUrl = baseUrl;
|
|
71
|
+
this.secret = secret;
|
|
72
|
+
this.product = product;
|
|
73
|
+
}
|
|
74
|
+
// ── Internal helpers ──────────────────────────────────────────────────────
|
|
75
|
+
_headers() {
|
|
76
|
+
return {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'X-Portal-Credit-Secret': this.secret,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async _post(path, body) {
|
|
82
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: this._headers(),
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
signal: AbortSignal.timeout(10_000),
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`Portal credit API error (${response.status}) at ${path}`);
|
|
90
|
+
}
|
|
91
|
+
return response.json();
|
|
92
|
+
}
|
|
93
|
+
// ── BillingProvider interface ─────────────────────────────────────────────
|
|
94
|
+
async checkLimit(userId, hasPersonalKey) {
|
|
95
|
+
if (hasPersonalKey) {
|
|
96
|
+
return { ok: true, remaining: Infinity, topUpBalance: 0, totalAvailable: Infinity, useTopUp: false };
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const result = await this._post('/api/credits/check', { userEmail: userId, product: this.product });
|
|
100
|
+
return {
|
|
101
|
+
ok: result.ok,
|
|
102
|
+
remaining: result.remaining ?? 0,
|
|
103
|
+
topUpBalance: result.topUpBalance ?? 0,
|
|
104
|
+
totalAvailable: result.totalAvailable ?? 0,
|
|
105
|
+
useTopUp: !result.ok ? false : (result.remaining ?? 1) <= 0,
|
|
106
|
+
...(!result.ok && result.reason ? { reason: result.reason === 'no_subscription' ? 'no_subscription' : 'limit_reached' } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Network failure — fail open to avoid blocking AI calls due to Portal downtime.
|
|
111
|
+
return { ok: true, remaining: 1, topUpBalance: 0, totalAvailable: 1, useTopUp: false };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async getUsage(userId) {
|
|
115
|
+
// Portal does not expose per-product usage history via the credit API.
|
|
116
|
+
// Return a minimal placeholder — products that need full usage dashboards
|
|
117
|
+
// should call Portal's management API directly.
|
|
118
|
+
void userId;
|
|
119
|
+
return {
|
|
120
|
+
currentPeriod: _currentPeriod(),
|
|
121
|
+
periodEnd: _thirtyDaysFromNow(),
|
|
122
|
+
messagesUsed: 0,
|
|
123
|
+
inputTokens: 0,
|
|
124
|
+
outputTokens: 0,
|
|
125
|
+
byModel: {},
|
|
126
|
+
lastUpdated: new Date().toISOString(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async getUsageStatus(userId, hasPersonalKey) {
|
|
130
|
+
const check = await this.checkLimit(userId, hasPersonalKey);
|
|
131
|
+
const usage = await this.getUsage(userId);
|
|
132
|
+
if (hasPersonalKey) {
|
|
133
|
+
return {
|
|
134
|
+
mode: 'byok',
|
|
135
|
+
usage,
|
|
136
|
+
limit: null,
|
|
137
|
+
remaining: null,
|
|
138
|
+
topUpBalance: 0,
|
|
139
|
+
totalAvailable: null,
|
|
140
|
+
percentUsed: null,
|
|
141
|
+
resetsAt: usage.periodEnd,
|
|
142
|
+
estimatedCostUSD: 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
mode: check.ok ? 'included' : 'free',
|
|
147
|
+
usage,
|
|
148
|
+
limit: null, // Portal doesn't expose the plan limit via this API
|
|
149
|
+
remaining: check.remaining === Infinity ? null : check.remaining,
|
|
150
|
+
topUpBalance: check.topUpBalance,
|
|
151
|
+
totalAvailable: check.totalAvailable === Infinity ? null : check.totalAvailable,
|
|
152
|
+
percentUsed: null,
|
|
153
|
+
resetsAt: usage.periodEnd,
|
|
154
|
+
estimatedCostUSD: 0,
|
|
155
|
+
isFreeUser: !check.ok,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async incrementUsage(userId, inputTokens, outputTokens, model, _useTopUp) {
|
|
159
|
+
try {
|
|
160
|
+
await this._post('/api/credits/consume', {
|
|
161
|
+
userEmail: userId,
|
|
162
|
+
product: this.product,
|
|
163
|
+
inputTokens,
|
|
164
|
+
outputTokens,
|
|
165
|
+
model,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Fire-and-forget — a consume failure does not block the caller.
|
|
170
|
+
console.error('[SDK/billing] Portal consume failed — usage may be under-counted');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async batchIncrementUsage(userId, batch) {
|
|
174
|
+
try {
|
|
175
|
+
await this._post('/api/credits/batch', {
|
|
176
|
+
userEmail: userId,
|
|
177
|
+
product: this.product,
|
|
178
|
+
messageCount: batch.messageCount,
|
|
179
|
+
inputTokens: batch.inputTokens,
|
|
180
|
+
outputTokens: batch.outputTokens,
|
|
181
|
+
byModel: batch.byModel,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
console.error('[SDK/billing] Portal batch flush failed — usage may be under-counted');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async getSubscription(_userId) {
|
|
189
|
+
// Subscription data is not exposed via the credit API.
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
async hasActiveSubscription(userId) {
|
|
193
|
+
const check = await this.checkLimit(userId, false);
|
|
194
|
+
return check.ok;
|
|
195
|
+
}
|
|
196
|
+
async getTopUpBalance(userId) {
|
|
197
|
+
const check = await this.checkLimit(userId, false);
|
|
198
|
+
return check.topUpBalance;
|
|
199
|
+
}
|
|
200
|
+
async canAccessPaidFeature(userId) {
|
|
201
|
+
return this.hasActiveSubscription(userId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=portal-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-provider.js","sourceRoot":"","sources":["../../../src/modules/billing/portal-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,327 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module billing/types
|
|
3
|
-
* @description Type definitions for sdk.billing.*
|
|
4
|
-
*
|
|
3
|
+
* @description Type definitions for sdk.billing.* — usage metering, subscription
|
|
4
|
+
* management, top-up credits, and Stripe integration.
|
|
5
|
+
*
|
|
6
|
+
* The billing module provides:
|
|
7
|
+
* - **Usage metering**: track AI message counts and token consumption per user per period
|
|
8
|
+
* - **Limit enforcement**: check whether a user can make another AI request
|
|
9
|
+
* - **Stripe subscriptions**: create checkout sessions, manage subscriptions, customer portal
|
|
10
|
+
* - **Top-up credits**: purchase additional credits beyond the monthly allowance
|
|
11
|
+
* - **Provider abstraction**: local file storage (dev) or Firestore (production)
|
|
12
|
+
*
|
|
13
|
+
* The billing provider is selected automatically (priority order):
|
|
14
|
+
* 1. `PORTAL_CREDIT_SECRET` set → `PortalBillingProvider` (cross-product unified credits)
|
|
15
|
+
* 2. `FIREBASE_SERVICE_ACCOUNT_KEY` set → `FirestoreBillingProvider` (per-product production)
|
|
16
|
+
* 3. Neither → `LocalBillingProvider` (dev, stores state in `.soulcraft/billing/`)
|
|
17
|
+
*
|
|
18
|
+
* @example Checking limits before an AI call
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const check = await sdk.billing.checkLimit(user.id, user.hasByok)
|
|
21
|
+
* if (!check.ok) return c.json({ error: 'Monthly limit reached', reason: check.reason }, 402)
|
|
22
|
+
* const response = await sdk.ai.complete(...)
|
|
23
|
+
* sdk.billing.recordUsage(user.id, response.usage.inputTokens, response.usage.outputTokens, response.model)
|
|
24
|
+
* ```
|
|
5
25
|
*/
|
|
6
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Soulcraft subscription plan identifier.
|
|
28
|
+
*
|
|
29
|
+
* - `free` — No subscription; limited monthly message allowance
|
|
30
|
+
* - `standard` — Paid subscription with included monthly credits
|
|
31
|
+
* - `byok` — Bring Your Own Key; unlimited usage with personal Anthropic key
|
|
32
|
+
*/
|
|
33
|
+
export type BillingPlanType = 'free' | 'standard' | 'byok';
|
|
34
|
+
/**
|
|
35
|
+
* Stripe/Firestore subscription status.
|
|
36
|
+
*/
|
|
37
|
+
export type SubscriptionStatus = 'active' | 'past_due' | 'canceled' | 'trialing';
|
|
38
|
+
/**
|
|
39
|
+
* Per-model token usage breakdown.
|
|
40
|
+
*/
|
|
41
|
+
export interface ModelUsage {
|
|
42
|
+
/** Input tokens consumed on this model. */
|
|
43
|
+
input: number;
|
|
44
|
+
/** Output tokens consumed on this model. */
|
|
45
|
+
output: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Aggregated usage data for a user in a billing period.
|
|
49
|
+
*/
|
|
50
|
+
export interface UsageData {
|
|
51
|
+
/** Billing period identifier in `'YYYY-MM'` format (for display only). */
|
|
52
|
+
currentPeriod: string;
|
|
53
|
+
/**
|
|
54
|
+
* ISO 8601 date when the current period ends and usage resets.
|
|
55
|
+
* Anniversary-based (tied to subscription renewal date, not calendar month).
|
|
56
|
+
*/
|
|
57
|
+
periodEnd: string;
|
|
58
|
+
/** Total AI messages sent this period. */
|
|
59
|
+
messagesUsed: number;
|
|
60
|
+
/** Total input tokens consumed this period across all models. */
|
|
61
|
+
inputTokens: number;
|
|
62
|
+
/** Total output tokens consumed this period across all models. */
|
|
63
|
+
outputTokens: number;
|
|
64
|
+
/** Per-model breakdown of token usage. Keys are model IDs. */
|
|
65
|
+
byModel: Record<string, ModelUsage>;
|
|
66
|
+
/** ISO 8601 timestamp of the last usage update. */
|
|
67
|
+
lastUpdated: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Stripe subscription state stored in the billing backend.
|
|
71
|
+
*/
|
|
72
|
+
export interface SubscriptionData {
|
|
73
|
+
/** Stripe customer ID (e.g. `'cus_...'`). */
|
|
74
|
+
stripeCustomerId?: string;
|
|
75
|
+
/** Stripe subscription ID (e.g. `'sub_...'`). */
|
|
76
|
+
stripeSubscriptionId?: string;
|
|
77
|
+
/** Active plan. */
|
|
78
|
+
plan: BillingPlanType;
|
|
79
|
+
/** Stripe subscription status. */
|
|
80
|
+
status: SubscriptionStatus;
|
|
81
|
+
/**
|
|
82
|
+
* ISO 8601 timestamp when the current billing period ends.
|
|
83
|
+
* Used to determine the anniversary-based reset date.
|
|
84
|
+
*/
|
|
85
|
+
currentPeriodEnd?: string;
|
|
86
|
+
/** Whether the subscription is set to cancel at the end of the current period. */
|
|
87
|
+
cancelAtPeriodEnd?: boolean;
|
|
88
|
+
/** Admin-assigned gift: the staff member's user ID who granted this subscription. */
|
|
89
|
+
giftedBy?: string;
|
|
90
|
+
/** ISO 8601 timestamp when the gift was granted. */
|
|
91
|
+
giftedAt?: string;
|
|
92
|
+
/** ISO 8601 timestamp when a gift subscription expires. */
|
|
93
|
+
expiresAt?: string;
|
|
94
|
+
/** Admin note (e.g. `'Beta tester'`, `'Partner: Acme Corp'`). */
|
|
95
|
+
note?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Result of a pre-request credit limit check.
|
|
99
|
+
*/
|
|
100
|
+
export interface LimitCheckResult {
|
|
101
|
+
/** Whether the user is allowed to make an AI request. */
|
|
102
|
+
ok: boolean;
|
|
103
|
+
/** Remaining monthly credits (excluding top-ups). */
|
|
104
|
+
remaining: number;
|
|
105
|
+
/** Top-up credit balance. */
|
|
106
|
+
topUpBalance: number;
|
|
107
|
+
/** Total available credits (`remaining + topUpBalance`). */
|
|
108
|
+
totalAvailable: number;
|
|
109
|
+
/** When `true`, this request should draw from top-up balance, not monthly allowance. */
|
|
110
|
+
useTopUp: boolean;
|
|
111
|
+
/** Reason for denial when `ok` is `false`. */
|
|
112
|
+
reason?: 'limit_reached' | 'no_subscription';
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Full usage status for a user — suitable for billing dashboards and quota displays.
|
|
116
|
+
*/
|
|
117
|
+
export interface UsageStatus {
|
|
118
|
+
/** Whether this user is BYOK (`'byok'`), has included credits (`'included'`), or is on free tier (`'free'`). */
|
|
119
|
+
mode: 'byok' | 'included' | 'free';
|
|
120
|
+
/** Raw usage data for the current period. */
|
|
121
|
+
usage: UsageData;
|
|
122
|
+
/** Monthly credit limit. `null` for BYOK users (unlimited). */
|
|
123
|
+
limit: number | null;
|
|
124
|
+
/** Credits remaining this period. `null` for BYOK users. */
|
|
125
|
+
remaining: number | null;
|
|
126
|
+
/** Top-up credit balance. */
|
|
127
|
+
topUpBalance: number;
|
|
128
|
+
/** Total available (`remaining + topUpBalance`). `null` for BYOK users. */
|
|
129
|
+
totalAvailable: number | null;
|
|
130
|
+
/** Usage as a percentage of the monthly limit (0–100). `null` for BYOK users. */
|
|
131
|
+
percentUsed: number | null;
|
|
132
|
+
/** ISO 8601 date when the usage period resets. */
|
|
133
|
+
resetsAt: string;
|
|
134
|
+
/** Estimated cost in USD based on token counts and model pricing. */
|
|
135
|
+
estimatedCostUSD: number;
|
|
136
|
+
/** Current subscription details. */
|
|
137
|
+
subscription?: SubscriptionData;
|
|
138
|
+
/** Whether this user is on the free tier (no active subscription). */
|
|
139
|
+
isFreeUser?: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Options for creating a Stripe checkout session.
|
|
143
|
+
*/
|
|
144
|
+
export interface CreateCheckoutSessionOptions {
|
|
145
|
+
/** The Stripe price ID for the subscription plan. */
|
|
146
|
+
priceId: string;
|
|
147
|
+
/** URL to redirect to on successful checkout. */
|
|
148
|
+
successUrl: string;
|
|
149
|
+
/** URL to redirect to if the user cancels checkout. */
|
|
150
|
+
cancelUrl: string;
|
|
151
|
+
/** Pre-fill the customer email in the Stripe checkout form. */
|
|
152
|
+
customerEmail?: string;
|
|
153
|
+
/** Existing Stripe customer ID (skips the email pre-fill). */
|
|
154
|
+
customerId?: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Options for creating a Stripe customer portal session.
|
|
158
|
+
*/
|
|
159
|
+
export interface CreatePortalSessionOptions {
|
|
160
|
+
/** Stripe customer ID. */
|
|
161
|
+
customerId: string;
|
|
162
|
+
/** URL to redirect to when the customer closes the portal. */
|
|
163
|
+
returnUrl: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Internal billing provider interface. Implemented by `LocalBillingProvider`
|
|
167
|
+
* and `FirestoreBillingProvider`.
|
|
168
|
+
*
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
export interface BillingProvider {
|
|
172
|
+
checkLimit(userId: string, hasPersonalKey: boolean): Promise<LimitCheckResult>;
|
|
173
|
+
getUsage(userId: string): Promise<UsageData>;
|
|
174
|
+
getUsageStatus(userId: string, hasPersonalKey: boolean): Promise<UsageStatus>;
|
|
175
|
+
incrementUsage(userId: string, inputTokens: number, outputTokens: number, model: string, useTopUp: boolean): Promise<void>;
|
|
176
|
+
batchIncrementUsage(userId: string, batch: BatchIncrementData): Promise<void>;
|
|
177
|
+
getSubscription(userId: string): Promise<SubscriptionData | undefined>;
|
|
178
|
+
hasActiveSubscription(userId: string): Promise<boolean>;
|
|
179
|
+
getTopUpBalance(userId: string): Promise<number>;
|
|
180
|
+
canAccessPaidFeature(userId: string): Promise<boolean>;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Accumulated usage data for a batch flush.
|
|
184
|
+
* @internal
|
|
185
|
+
*/
|
|
186
|
+
export interface BatchIncrementData {
|
|
187
|
+
messageCount: number;
|
|
188
|
+
inputTokens: number;
|
|
189
|
+
outputTokens: number;
|
|
190
|
+
topUpUsed: number;
|
|
191
|
+
byModel: Record<string, {
|
|
192
|
+
input: number;
|
|
193
|
+
output: number;
|
|
194
|
+
}>;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The `sdk.billing.*` namespace — usage metering, subscriptions, and top-up credits.
|
|
198
|
+
*/
|
|
199
|
+
export interface BillingModule {
|
|
200
|
+
/**
|
|
201
|
+
* Check whether a user has credits available for an AI request.
|
|
202
|
+
*
|
|
203
|
+
* Returns immediately from cache where possible. BYOK users (`hasPersonalKey: true`)
|
|
204
|
+
* always return `{ ok: true }`.
|
|
205
|
+
*
|
|
206
|
+
* @param userId - The authenticated user's ID.
|
|
207
|
+
* @param hasPersonalKey - Whether this user has a BYOK Anthropic API key.
|
|
208
|
+
* @returns Credit availability result including remaining balance.
|
|
209
|
+
*/
|
|
210
|
+
checkLimit(userId: string, hasPersonalKey: boolean): Promise<LimitCheckResult>;
|
|
211
|
+
/**
|
|
212
|
+
* Record AI usage for a user after a completed model call.
|
|
213
|
+
*
|
|
214
|
+
* This is fire-and-forget — buffered in memory and flushed to the billing
|
|
215
|
+
* backend every 5 seconds. Does not block the caller.
|
|
216
|
+
*
|
|
217
|
+
* @param userId - The authenticated user's ID.
|
|
218
|
+
* @param inputTokens - Input tokens consumed.
|
|
219
|
+
* @param outputTokens - Output tokens consumed.
|
|
220
|
+
* @param model - Model ID (e.g. `'claude-haiku-4-5-20251001'`).
|
|
221
|
+
*/
|
|
222
|
+
recordUsage(userId: string, inputTokens: number, outputTokens: number, model: string): void;
|
|
223
|
+
/**
|
|
224
|
+
* Get the current usage data for a user.
|
|
225
|
+
*
|
|
226
|
+
* @param userId - The authenticated user's ID.
|
|
227
|
+
* @returns Usage totals for the current billing period.
|
|
228
|
+
*/
|
|
229
|
+
getUsage(userId: string): Promise<UsageData>;
|
|
230
|
+
/**
|
|
231
|
+
* Get the full usage status for a user, suitable for billing dashboards.
|
|
232
|
+
*
|
|
233
|
+
* @param userId - The authenticated user's ID.
|
|
234
|
+
* @param hasPersonalKey - Whether this user has a BYOK key.
|
|
235
|
+
* @returns Detailed usage status with limits, remaining, and cost estimate.
|
|
236
|
+
*/
|
|
237
|
+
getUsageStatus(userId: string, hasPersonalKey: boolean): Promise<UsageStatus>;
|
|
238
|
+
/**
|
|
239
|
+
* Get the active Stripe subscription for a user.
|
|
240
|
+
*
|
|
241
|
+
* @param userId - The authenticated user's ID.
|
|
242
|
+
* @returns Subscription data, or `undefined` if the user has no subscription.
|
|
243
|
+
*/
|
|
244
|
+
getSubscription(userId: string): Promise<SubscriptionData | undefined>;
|
|
245
|
+
/**
|
|
246
|
+
* Check whether a user has an active Stripe subscription.
|
|
247
|
+
*
|
|
248
|
+
* @param userId - The authenticated user's ID.
|
|
249
|
+
* @returns `true` if the user has an active or trialing subscription.
|
|
250
|
+
*/
|
|
251
|
+
hasActiveSubscription(userId: string): Promise<boolean>;
|
|
252
|
+
/**
|
|
253
|
+
* Create a Stripe checkout session for a new subscription.
|
|
254
|
+
*
|
|
255
|
+
* @param options - Checkout session parameters.
|
|
256
|
+
* @returns The Stripe checkout session URL to redirect the user to.
|
|
257
|
+
*
|
|
258
|
+
* @throws When `STRIPE_SECRET_KEY` is not configured.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* const url = await sdk.billing.createCheckoutSession({
|
|
263
|
+
* priceId: 'price_...',
|
|
264
|
+
* successUrl: 'https://app.example.com/billing/success',
|
|
265
|
+
* cancelUrl: 'https://app.example.com/billing',
|
|
266
|
+
* customerEmail: user.email,
|
|
267
|
+
* })
|
|
268
|
+
* return c.redirect(url)
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
createCheckoutSession(options: CreateCheckoutSessionOptions): Promise<string>;
|
|
272
|
+
/**
|
|
273
|
+
* Create a Stripe customer portal session for managing subscriptions.
|
|
274
|
+
*
|
|
275
|
+
* @param options - Portal session parameters.
|
|
276
|
+
* @returns The Stripe portal session URL to redirect the user to.
|
|
277
|
+
*
|
|
278
|
+
* @throws When `STRIPE_SECRET_KEY` is not configured.
|
|
279
|
+
*/
|
|
280
|
+
createPortalSession(options: CreatePortalSessionOptions): Promise<string>;
|
|
281
|
+
/**
|
|
282
|
+
* Cancel a user's active Stripe subscription at the end of the current period.
|
|
283
|
+
*
|
|
284
|
+
* @param userId - The authenticated user's ID.
|
|
285
|
+
* @throws When the user has no active subscription or Stripe is not configured.
|
|
286
|
+
*/
|
|
287
|
+
cancelSubscription(userId: string): Promise<void>;
|
|
288
|
+
/**
|
|
289
|
+
* Get the current top-up credit balance for a user.
|
|
290
|
+
*
|
|
291
|
+
* @param userId - The authenticated user's ID.
|
|
292
|
+
* @returns The number of purchased top-up credits remaining.
|
|
293
|
+
*/
|
|
294
|
+
getTopUpBalance(userId: string): Promise<number>;
|
|
295
|
+
/**
|
|
296
|
+
* Add top-up credits to a user's balance.
|
|
297
|
+
*
|
|
298
|
+
* Typically called from a Stripe webhook handler after a successful one-time purchase.
|
|
299
|
+
*
|
|
300
|
+
* @param userId - The authenticated user's ID.
|
|
301
|
+
* @param amount - Number of credits to add.
|
|
302
|
+
* @returns The new top-up balance after adding credits.
|
|
303
|
+
*/
|
|
304
|
+
addTopUp(userId: string, amount: number): Promise<number>;
|
|
305
|
+
/**
|
|
306
|
+
* Check whether a user can access paid features.
|
|
307
|
+
*
|
|
308
|
+
* Returns `true` for active subscribers and BYOK users; `false` for free tier
|
|
309
|
+
* users who have exhausted their allowance.
|
|
310
|
+
*
|
|
311
|
+
* @param userId - The authenticated user's ID.
|
|
312
|
+
*/
|
|
313
|
+
canAccessPaidFeature(userId: string): Promise<boolean>;
|
|
314
|
+
/**
|
|
315
|
+
* Flush all pending usage increments to the billing backend immediately.
|
|
316
|
+
*
|
|
317
|
+
* Called automatically every 5 seconds and on graceful shutdown.
|
|
318
|
+
* Safe to call manually (e.g. before a deployment restart).
|
|
319
|
+
*/
|
|
320
|
+
flush(): Promise<void>;
|
|
321
|
+
/**
|
|
322
|
+
* Stop the background flush interval.
|
|
323
|
+
* Call this during graceful shutdown to avoid keeping the process alive.
|
|
324
|
+
*/
|
|
325
|
+
stopFlush(): void;
|
|
326
|
+
}
|
|
7
327
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module billing/types
|
|
3
|
-
* @description Type definitions for sdk.billing.*
|
|
4
|
-
*
|
|
3
|
+
* @description Type definitions for sdk.billing.* — usage metering, subscription
|
|
4
|
+
* management, top-up credits, and Stripe integration.
|
|
5
|
+
*
|
|
6
|
+
* The billing module provides:
|
|
7
|
+
* - **Usage metering**: track AI message counts and token consumption per user per period
|
|
8
|
+
* - **Limit enforcement**: check whether a user can make another AI request
|
|
9
|
+
* - **Stripe subscriptions**: create checkout sessions, manage subscriptions, customer portal
|
|
10
|
+
* - **Top-up credits**: purchase additional credits beyond the monthly allowance
|
|
11
|
+
* - **Provider abstraction**: local file storage (dev) or Firestore (production)
|
|
12
|
+
*
|
|
13
|
+
* The billing provider is selected automatically (priority order):
|
|
14
|
+
* 1. `PORTAL_CREDIT_SECRET` set → `PortalBillingProvider` (cross-product unified credits)
|
|
15
|
+
* 2. `FIREBASE_SERVICE_ACCOUNT_KEY` set → `FirestoreBillingProvider` (per-product production)
|
|
16
|
+
* 3. Neither → `LocalBillingProvider` (dev, stores state in `.soulcraft/billing/`)
|
|
17
|
+
*
|
|
18
|
+
* @example Checking limits before an AI call
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const check = await sdk.billing.checkLimit(user.id, user.hasByok)
|
|
21
|
+
* if (!check.ok) return c.json({ error: 'Monthly limit reached', reason: check.reason }, 402)
|
|
22
|
+
* const response = await sdk.ai.complete(...)
|
|
23
|
+
* sdk.billing.recordUsage(user.id, response.usage.inputTokens, response.usage.outputTokens, response.model)
|
|
24
|
+
* ```
|
|
5
25
|
*/
|
|
6
26
|
export {};
|
|
7
27
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/billing/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/billing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module billing/usage-buffer
|
|
3
|
+
* @description Fire-and-forget write-behind buffer for AI usage tracking.
|
|
4
|
+
*
|
|
5
|
+
* The usage buffer accumulates per-user token increments in memory and flushes
|
|
6
|
+
* them to the billing provider in a single batch write every 5 seconds. This
|
|
7
|
+
* pattern eliminates billing writes from the critical path of AI responses —
|
|
8
|
+
* the SSE `done` event is emitted before any billing I/O.
|
|
9
|
+
*
|
|
10
|
+
* Up to 5 seconds of usage may be lost on unclean process exits. This is
|
|
11
|
+
* acceptable for the billing use case — users are not double-charged on restart,
|
|
12
|
+
* and a few messages of under-counting per process lifetime is tolerable.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const buffer = new UsageBuffer(billingProvider)
|
|
17
|
+
*
|
|
18
|
+
* // Fire-and-forget — never await this:
|
|
19
|
+
* buffer.increment('user-123', 500, 200, 'claude-haiku-4-5-20251001', false)
|
|
20
|
+
*
|
|
21
|
+
* // On graceful shutdown:
|
|
22
|
+
* await buffer.flush()
|
|
23
|
+
* buffer.stop()
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import type { BillingProvider } from './types.js';
|
|
27
|
+
/**
|
|
28
|
+
* Write-behind usage buffer.
|
|
29
|
+
*
|
|
30
|
+
* Thread-safe within a single Node.js/Bun process (single-threaded event loop).
|
|
31
|
+
* Accumulates increments in memory and flushes to the billing provider on a
|
|
32
|
+
* 5-second interval. Also exposes `flush()` for explicit drain on shutdown.
|
|
33
|
+
*/
|
|
34
|
+
export declare class UsageBuffer {
|
|
35
|
+
private readonly provider;
|
|
36
|
+
private readonly pending;
|
|
37
|
+
private _timer;
|
|
38
|
+
/**
|
|
39
|
+
* @param provider - The billing provider to flush to.
|
|
40
|
+
*/
|
|
41
|
+
constructor(provider: BillingProvider);
|
|
42
|
+
/**
|
|
43
|
+
* Record a usage increment for a user.
|
|
44
|
+
*
|
|
45
|
+
* This method is synchronous and never throws. Usage is accumulated in memory
|
|
46
|
+
* and written to the billing backend on the next flush.
|
|
47
|
+
*
|
|
48
|
+
* @param userId - The authenticated user's ID.
|
|
49
|
+
* @param inputTokens - Input tokens consumed.
|
|
50
|
+
* @param outputTokens - Output tokens consumed.
|
|
51
|
+
* @param model - Model ID (e.g. `'claude-haiku-4-5-20251001'`).
|
|
52
|
+
* @param useTopUp - Whether this message drew from the top-up balance.
|
|
53
|
+
*/
|
|
54
|
+
increment(userId: string, inputTokens: number, outputTokens: number, model: string, useTopUp: boolean): void;
|
|
55
|
+
/**
|
|
56
|
+
* Flush all pending usage increments to the billing provider.
|
|
57
|
+
*
|
|
58
|
+
* Called automatically on the 5-second interval, and on explicit calls from
|
|
59
|
+
* the billing module's `flush()` method (e.g. graceful shutdown).
|
|
60
|
+
*
|
|
61
|
+
* Errors are caught and logged — a flush failure never propagates to callers.
|
|
62
|
+
*/
|
|
63
|
+
flush(): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Stop the background flush interval.
|
|
66
|
+
* Call this during graceful shutdown after flushing.
|
|
67
|
+
*/
|
|
68
|
+
stop(): void;
|
|
69
|
+
private _start;
|
|
70
|
+
private _requeue;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=usage-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-buffer.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/usage-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,YAAY,CAAA;AAcrE;;;;;;GAMG;AACH,qBAAa,WAAW;IAOV,OAAO,CAAC,QAAQ,CAAC,QAAQ;IANrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,MAAM,CAA4C;IAE1D;;OAEG;gBAC0B,QAAQ,EAAE,eAAe;IAItD;;;;;;;;;;;OAWG;IACH,SAAS,CACP,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GAChB,IAAI;IAmBP;;;;;;;OAOG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B5B;;;OAGG;IACH,IAAI,IAAI,IAAI;IASZ,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,QAAQ;CAgBjB"}
|