@carrierllc/mcp 0.1.0 → 0.2.1
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/.metadata_never_index +0 -0
- package/dist/apps/app-state.d.ts +19 -0
- package/dist/apps/app-state.d.ts.map +1 -0
- package/dist/apps/app-state.js +28 -0
- package/dist/apps/app-state.js.map +1 -0
- package/dist/apps/balance-topup.d.ts +19 -0
- package/dist/apps/balance-topup.d.ts.map +1 -0
- package/dist/apps/balance-topup.js +121 -0
- package/dist/apps/balance-topup.js.map +1 -0
- package/dist/apps/fleet-health-app.d.ts +29 -0
- package/dist/apps/fleet-health-app.d.ts.map +1 -0
- package/dist/apps/fleet-health-app.js +152 -0
- package/dist/apps/fleet-health-app.js.map +1 -0
- package/dist/apps/index.d.ts +37 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +42 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/provisioning-wizard.d.ts +11 -0
- package/dist/apps/provisioning-wizard.d.ts.map +1 -0
- package/dist/apps/provisioning-wizard.js +345 -0
- package/dist/apps/provisioning-wizard.js.map +1 -0
- package/dist/audit.d.ts +31 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +42 -0
- package/dist/audit.js.map +1 -0
- package/dist/billing-thresholds.d.ts +71 -0
- package/dist/billing-thresholds.d.ts.map +1 -0
- package/dist/billing-thresholds.js +238 -0
- package/dist/billing-thresholds.js.map +1 -0
- package/dist/billing.d.ts +95 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +239 -0
- package/dist/billing.js.map +1 -0
- package/dist/clerk.d.ts +125 -0
- package/dist/clerk.d.ts.map +1 -0
- package/dist/clerk.js +389 -0
- package/dist/clerk.js.map +1 -0
- package/dist/client.d.ts +12 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -53
- package/dist/client.js.map +1 -1
- package/dist/credits.d.ts +165 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +483 -0
- package/dist/credits.js.map +1 -0
- package/dist/index.d.ts +6 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -23
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -8
- package/dist/intelligence.d.ts.map +1 -1
- package/dist/intelligence.js +399 -154
- package/dist/intelligence.js.map +1 -1
- package/dist/list-recent-ocs-events.d.ts +48 -0
- package/dist/list-recent-ocs-events.d.ts.map +1 -0
- package/dist/list-recent-ocs-events.js +160 -0
- package/dist/list-recent-ocs-events.js.map +1 -0
- package/dist/manus-client.d.ts +429 -0
- package/dist/manus-client.d.ts.map +1 -0
- package/dist/manus-client.js +401 -0
- package/dist/manus-client.js.map +1 -0
- package/dist/manus-common.d.ts +5 -0
- package/dist/manus-common.d.ts.map +1 -0
- package/dist/manus-common.js +5 -0
- package/dist/manus-common.js.map +1 -0
- package/dist/manus-schedule.d.ts +74 -0
- package/dist/manus-schedule.d.ts.map +1 -0
- package/dist/manus-schedule.js +213 -0
- package/dist/manus-schedule.js.map +1 -0
- package/dist/manus-usage.d.ts +32 -0
- package/dist/manus-usage.d.ts.map +1 -0
- package/dist/manus-usage.js +120 -0
- package/dist/manus-usage.js.map +1 -0
- package/dist/manus-webhook.d.ts +60 -0
- package/dist/manus-webhook.d.ts.map +1 -0
- package/dist/manus-webhook.js +361 -0
- package/dist/manus-webhook.js.map +1 -0
- package/dist/pricing-tools.d.ts +26 -0
- package/dist/pricing-tools.d.ts.map +1 -0
- package/dist/pricing-tools.js +428 -0
- package/dist/pricing-tools.js.map +1 -0
- package/dist/projects-tools.d.ts +39 -0
- package/dist/projects-tools.d.ts.map +1 -0
- package/dist/projects-tools.js +545 -0
- package/dist/projects-tools.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +1 -0
- package/dist/prompts.js.map +1 -1
- package/dist/stripe-connect-tools.d.ts +33 -0
- package/dist/stripe-connect-tools.d.ts.map +1 -0
- package/dist/stripe-connect-tools.js +504 -0
- package/dist/stripe-connect-tools.js.map +1 -0
- package/dist/timing-safe-equal.d.ts +5 -0
- package/dist/timing-safe-equal.d.ts.map +1 -0
- package/dist/timing-safe-equal.js +13 -0
- package/dist/timing-safe-equal.js.map +1 -0
- package/dist/tools-backlog.d.ts +37 -0
- package/dist/tools-backlog.d.ts.map +1 -0
- package/dist/tools-backlog.js +410 -0
- package/dist/tools-backlog.js.map +1 -0
- package/dist/tools-carrier-ask.d.ts +138 -0
- package/dist/tools-carrier-ask.d.ts.map +1 -0
- package/dist/tools-carrier-ask.js +913 -0
- package/dist/tools-carrier-ask.js.map +1 -0
- package/dist/tools-pricing.d.ts +18 -0
- package/dist/tools-pricing.d.ts.map +1 -0
- package/dist/tools-pricing.js +172 -0
- package/dist/tools-pricing.js.map +1 -0
- package/dist/tools-ui-agent-ask.d.ts +29 -0
- package/dist/tools-ui-agent-ask.d.ts.map +1 -0
- package/dist/tools-ui-agent-ask.js +272 -0
- package/dist/tools-ui-agent-ask.js.map +1 -0
- package/dist/tools-ui-agent-schedule.d.ts +29 -0
- package/dist/tools-ui-agent-schedule.d.ts.map +1 -0
- package/dist/tools-ui-agent-schedule.js +517 -0
- package/dist/tools-ui-agent-schedule.js.map +1 -0
- package/dist/tools-ui-agent.d.ts +40 -0
- package/dist/tools-ui-agent.d.ts.map +1 -0
- package/dist/tools-ui-agent.js +508 -0
- package/dist/tools-ui-agent.js.map +1 -0
- package/dist/tools.d.ts +44 -13
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +892 -210
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +298 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +15 -8
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrier MCP — Billing Thresholds (Hex Pattern)
|
|
3
|
+
*
|
|
4
|
+
* Prevents bill shock by:
|
|
5
|
+
* 1. Triggering invoices at predefined spend levels
|
|
6
|
+
* 2. Sending notifications at configurable usage percentages
|
|
7
|
+
* 3. Hard-stopping overages at a user-defined maximum
|
|
8
|
+
*
|
|
9
|
+
* Integrates with Stripe:
|
|
10
|
+
* - Uses Stripe Billing Thresholds on subscriptions
|
|
11
|
+
* - Creates threshold-triggered invoices for overage accumulation
|
|
12
|
+
* - Sends webhook events for notification dispatch
|
|
13
|
+
*
|
|
14
|
+
* Reference: Hex case study — "Mitigating the risk of bill shock"
|
|
15
|
+
* - Invoices trigger automatically at predefined amounts
|
|
16
|
+
* - Acts as spend alerts, preventing unexpected charges
|
|
17
|
+
*/
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Default Configs
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
const DEFAULT_THRESHOLD_CONFIGS = {
|
|
22
|
+
free: {
|
|
23
|
+
notification_thresholds: [80, 95, 100],
|
|
24
|
+
hard_cap_cents: 0, // Free tier has no overages
|
|
25
|
+
invoice_threshold_cents: 0,
|
|
26
|
+
auto_pause_on_cap: true,
|
|
27
|
+
notification_email: null,
|
|
28
|
+
webhook_url: null,
|
|
29
|
+
},
|
|
30
|
+
pro: {
|
|
31
|
+
notification_thresholds: [50, 75, 90, 100],
|
|
32
|
+
hard_cap_cents: 50_000, // $500 hard cap default
|
|
33
|
+
invoice_threshold_cents: 10_000, // Invoice every $100
|
|
34
|
+
auto_pause_on_cap: false,
|
|
35
|
+
notification_email: null,
|
|
36
|
+
webhook_url: null,
|
|
37
|
+
},
|
|
38
|
+
enterprise: {
|
|
39
|
+
notification_thresholds: [75, 90],
|
|
40
|
+
hard_cap_cents: 0, // No cap for enterprise
|
|
41
|
+
invoice_threshold_cents: 100_000, // Invoice every $1,000
|
|
42
|
+
auto_pause_on_cap: false,
|
|
43
|
+
notification_email: null,
|
|
44
|
+
webhook_url: null,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Core Functions
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Check billing thresholds before allowing overage consumption.
|
|
52
|
+
* Returns whether overage is still allowed and any triggered events.
|
|
53
|
+
*/
|
|
54
|
+
export async function checkBillingThresholds(env, sub, tier, currentSpendCents, additionalSpendCents) {
|
|
55
|
+
const config = await getThresholdConfig(env, sub, tier);
|
|
56
|
+
const events = [];
|
|
57
|
+
const projectedSpend = currentSpendCents + additionalSpendCents;
|
|
58
|
+
// Check hard cap
|
|
59
|
+
if (config.hard_cap_cents > 0 && projectedSpend >= config.hard_cap_cents) {
|
|
60
|
+
events.push({
|
|
61
|
+
type: "hard_cap_reached",
|
|
62
|
+
sub,
|
|
63
|
+
tier,
|
|
64
|
+
current_spend_cents: projectedSpend,
|
|
65
|
+
threshold_cents: config.hard_cap_cents,
|
|
66
|
+
pct_of_limit: 100,
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
if (config.auto_pause_on_cap) {
|
|
70
|
+
events.push({
|
|
71
|
+
type: "overage_paused",
|
|
72
|
+
sub,
|
|
73
|
+
tier,
|
|
74
|
+
current_spend_cents: projectedSpend,
|
|
75
|
+
threshold_cents: config.hard_cap_cents,
|
|
76
|
+
pct_of_limit: 100,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
overage_allowed: false,
|
|
81
|
+
events,
|
|
82
|
+
spend_pct: 100,
|
|
83
|
+
remaining_cents: 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Check invoice threshold
|
|
88
|
+
if (config.invoice_threshold_cents > 0) {
|
|
89
|
+
const prevInvoiceCount = Math.floor(currentSpendCents / config.invoice_threshold_cents);
|
|
90
|
+
const newInvoiceCount = Math.floor(projectedSpend / config.invoice_threshold_cents);
|
|
91
|
+
if (newInvoiceCount > prevInvoiceCount) {
|
|
92
|
+
events.push({
|
|
93
|
+
type: "invoice_triggered",
|
|
94
|
+
sub,
|
|
95
|
+
tier,
|
|
96
|
+
current_spend_cents: projectedSpend,
|
|
97
|
+
threshold_cents: config.invoice_threshold_cents * newInvoiceCount,
|
|
98
|
+
pct_of_limit: config.hard_cap_cents > 0
|
|
99
|
+
? Math.round((projectedSpend / config.hard_cap_cents) * 100)
|
|
100
|
+
: 0,
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Check notification thresholds
|
|
106
|
+
if (config.hard_cap_cents > 0) {
|
|
107
|
+
for (const pct of config.notification_thresholds) {
|
|
108
|
+
const thresholdAmount = Math.round((pct / 100) * config.hard_cap_cents);
|
|
109
|
+
if (currentSpendCents < thresholdAmount && projectedSpend >= thresholdAmount) {
|
|
110
|
+
events.push({
|
|
111
|
+
type: "notification",
|
|
112
|
+
sub,
|
|
113
|
+
tier,
|
|
114
|
+
current_spend_cents: projectedSpend,
|
|
115
|
+
threshold_cents: thresholdAmount,
|
|
116
|
+
pct_of_limit: pct,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Dispatch events (fire-and-forget)
|
|
123
|
+
if (events.length > 0) {
|
|
124
|
+
void dispatchThresholdEvents(env, sub, events, config);
|
|
125
|
+
}
|
|
126
|
+
const spendPct = config.hard_cap_cents > 0
|
|
127
|
+
? Math.round((projectedSpend / config.hard_cap_cents) * 100)
|
|
128
|
+
: 0;
|
|
129
|
+
const remaining = config.hard_cap_cents > 0
|
|
130
|
+
? Math.max(0, config.hard_cap_cents - projectedSpend)
|
|
131
|
+
: Infinity;
|
|
132
|
+
return {
|
|
133
|
+
overage_allowed: true,
|
|
134
|
+
events,
|
|
135
|
+
spend_pct: spendPct,
|
|
136
|
+
remaining_cents: remaining,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get threshold config for a user, with fallback to tier defaults.
|
|
141
|
+
*/
|
|
142
|
+
export async function getThresholdConfig(env, sub, tier) {
|
|
143
|
+
const key = `threshold:config:${sub}`;
|
|
144
|
+
const raw = await env.CARRIER_USERS.get(key, "json").catch(() => null);
|
|
145
|
+
if (raw && typeof raw === "object" && "notification_thresholds" in raw) {
|
|
146
|
+
return raw;
|
|
147
|
+
}
|
|
148
|
+
return DEFAULT_THRESHOLD_CONFIGS[tier];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Update threshold config for a user.
|
|
152
|
+
*/
|
|
153
|
+
export async function updateThresholdConfig(env, sub, updates) {
|
|
154
|
+
const tier = await resolveUserTier(env, sub);
|
|
155
|
+
const current = await getThresholdConfig(env, sub, tier);
|
|
156
|
+
const updated = { ...current, ...updates };
|
|
157
|
+
const key = `threshold:config:${sub}`;
|
|
158
|
+
await env.CARRIER_USERS.put(key, JSON.stringify(updated));
|
|
159
|
+
return updated;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Set up Stripe subscription billing thresholds.
|
|
163
|
+
* Called when a user upgrades to Pro or changes their threshold config.
|
|
164
|
+
*/
|
|
165
|
+
export async function syncStripeThresholds(env, sub, config) {
|
|
166
|
+
const stripeKey = env.STRIPE_SECRET_KEY;
|
|
167
|
+
if (!stripeKey)
|
|
168
|
+
return false;
|
|
169
|
+
const subscriptionId = await env.CARRIER_USERS.get(`stripe_subscription_id:${sub}`);
|
|
170
|
+
if (!subscriptionId)
|
|
171
|
+
return false;
|
|
172
|
+
// Update Stripe subscription with billing thresholds
|
|
173
|
+
const body = new URLSearchParams();
|
|
174
|
+
if (config.invoice_threshold_cents > 0) {
|
|
175
|
+
body.set("billing_thresholds[amount_gte]", String(config.invoice_threshold_cents));
|
|
176
|
+
}
|
|
177
|
+
const resp = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: {
|
|
180
|
+
Authorization: `Bearer ${stripeKey}`,
|
|
181
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
182
|
+
},
|
|
183
|
+
body: body.toString(),
|
|
184
|
+
});
|
|
185
|
+
return resp.ok;
|
|
186
|
+
}
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Event Dispatch
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
/**
|
|
191
|
+
* Dispatch threshold events to configured notification channels.
|
|
192
|
+
*/
|
|
193
|
+
async function dispatchThresholdEvents(env, sub, events, config) {
|
|
194
|
+
// Store events in KV for UI display
|
|
195
|
+
const eventsKey = `threshold:events:${sub}`;
|
|
196
|
+
const existingRaw = await env.CARRIER_USERS.get(eventsKey, "json").catch(() => null);
|
|
197
|
+
const existing = Array.isArray(existingRaw) ? existingRaw : [];
|
|
198
|
+
const allEvents = [...existing, ...events].slice(-50); // Keep last 50 events
|
|
199
|
+
await env.CARRIER_USERS.put(eventsKey, JSON.stringify(allEvents), {
|
|
200
|
+
expirationTtl: 35 * 24 * 60 * 60,
|
|
201
|
+
});
|
|
202
|
+
// Webhook dispatch
|
|
203
|
+
if (config.webhook_url) {
|
|
204
|
+
await fetch(config.webhook_url, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "Content-Type": "application/json" },
|
|
207
|
+
body: JSON.stringify({ events }),
|
|
208
|
+
}).catch(() => { });
|
|
209
|
+
}
|
|
210
|
+
// Audit log (Analytics Engine)
|
|
211
|
+
for (const event of events) {
|
|
212
|
+
const ae = env.AUDIT_LOG;
|
|
213
|
+
if (ae) {
|
|
214
|
+
ae.writeDataPoint({
|
|
215
|
+
blobs: [
|
|
216
|
+
`threshold_${event.type}`,
|
|
217
|
+
sub,
|
|
218
|
+
event.tier,
|
|
219
|
+
],
|
|
220
|
+
doubles: [event.current_spend_cents, event.threshold_cents],
|
|
221
|
+
indexes: [sub],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Helpers
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
async function resolveUserTier(env, sub) {
|
|
230
|
+
const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
|
|
231
|
+
if (raw && typeof raw === "object" && "tier" in raw) {
|
|
232
|
+
const t = raw.tier;
|
|
233
|
+
if (t === "pro" || t === "enterprise")
|
|
234
|
+
return t;
|
|
235
|
+
}
|
|
236
|
+
return "free";
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=billing-thresholds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing-thresholds.js","sourceRoot":"","sources":["../src/billing-thresholds.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrier MCP — Billing v2.0 (Phase 15 — Clerk Billing GA Migration)
|
|
3
|
+
*
|
|
4
|
+
* Tier model:
|
|
5
|
+
* free — 5,000 tool calls/mo, read scope only, no Stripe product
|
|
6
|
+
* pro — 50,000 tool calls/mo, read+write+intelligence, $49/mo
|
|
7
|
+
* enterprise — unlimited tool calls, read+write+admin, $499/mo, custom rate limits, SSO
|
|
8
|
+
*
|
|
9
|
+
* Storage layout (CARRIER_USERS KV):
|
|
10
|
+
* key: user:<sub> value: UserRecord (tier field drives all gate checks)
|
|
11
|
+
* key: usage:<sub>:<yyyymm> value: JSON { calls: number } (TTL: 35 days)
|
|
12
|
+
*
|
|
13
|
+
* Analytics Engine (carrier_mcp_audit dataset) is the source of truth for usage
|
|
14
|
+
* rollups — KV counter is a fast hot path for quota checks.
|
|
15
|
+
*
|
|
16
|
+
* BILLING_PRIMARY feature flag (Doppler carrier/prd):
|
|
17
|
+
* "clerk" → Clerk Billing is authoritative; KV is a cache/fallback.
|
|
18
|
+
* Portal redirects to Clerk-hosted billing page.
|
|
19
|
+
* Stripe webhook still runs (dual-write period; kept for 1 billing cycle).
|
|
20
|
+
* "stripe" → (default) Direct Stripe path; Clerk plan claim is a session-layer overlay.
|
|
21
|
+
*
|
|
22
|
+
* IMPORTANT: This module NEVER charges a card. It only:
|
|
23
|
+
* 1. Gates tool calls based on tier + call count.
|
|
24
|
+
* 2. Records usage records to Stripe Metered Billing (for Pro overages, Stripe-primary only).
|
|
25
|
+
* 3. Redirects users to the appropriate Billing Portal (Clerk or Stripe).
|
|
26
|
+
*/
|
|
27
|
+
import type { Env } from "./types.js";
|
|
28
|
+
export declare const TIER_CALL_LIMITS: Record<Tier, number>;
|
|
29
|
+
export declare const UPGRADE_URL = "https://mcp.carrier.llc/upgrade";
|
|
30
|
+
export type Tier = "free" | "pro" | "enterprise";
|
|
31
|
+
export type ScopeToken = "read" | "write" | "admin";
|
|
32
|
+
export declare const TIER_SCOPES: Record<Tier, ScopeToken[]>;
|
|
33
|
+
/**
|
|
34
|
+
* "clerk" → Clerk Billing is the authoritative source.
|
|
35
|
+
* "stripe" → (default) Direct Stripe path; Clerk plan claim is a session overlay only.
|
|
36
|
+
*
|
|
37
|
+
* Cutover recommendation: flip to "clerk" after one full billing cycle of
|
|
38
|
+
* dual-write validation confirming KV ↔ Clerk plan parity for all active subscribers.
|
|
39
|
+
*/
|
|
40
|
+
export type BillingPrimary = "clerk" | "stripe";
|
|
41
|
+
export declare function getBillingPrimary(env: Env): BillingPrimary;
|
|
42
|
+
/**
|
|
43
|
+
* Read the tier stored in the user's KV record.
|
|
44
|
+
* Falls back to "free" if the record is missing or malformed.
|
|
45
|
+
* When BILLING_PRIMARY=clerk this becomes a cache/fallback only.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getUserTier(env: Env, sub: string): Promise<Tier>;
|
|
48
|
+
/**
|
|
49
|
+
* Phase 15 — Resolve the effective tier.
|
|
50
|
+
*
|
|
51
|
+
* BILLING_PRIMARY=clerk: Clerk session plan claim is primary; KV is the fallback.
|
|
52
|
+
* BILLING_PRIMARY=stripe (default): KV is canonical; Clerk plan is an overlay
|
|
53
|
+
* (used to catch Clerk-driven downgrades like plan cancellations).
|
|
54
|
+
*
|
|
55
|
+
* In both modes: clerkPlan ?? fallbackTier — the function is the same; the
|
|
56
|
+
* caller decides which value to pass as fallbackTier based on BILLING_PRIMARY.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveTierFromClerk(clerkPlan: Tier | undefined, fallbackTier: Tier): Tier;
|
|
59
|
+
/**
|
|
60
|
+
* Phase 15 — Build the Clerk-hosted billing portal URL.
|
|
61
|
+
* Activated as primary when BILLING_PRIMARY=clerk.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildClerkBillingPortalUrl(env: Env, returnUrl: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Phase 15 — Resolve the billing portal URL based on BILLING_PRIMARY.
|
|
66
|
+
* This is the single entry point for all portal redirects (oauth/index.ts,
|
|
67
|
+
* console billing routes, etc.).
|
|
68
|
+
*
|
|
69
|
+
* BILLING_PRIMARY=clerk → Clerk-hosted portal (buildClerkBillingPortalUrl)
|
|
70
|
+
* BILLING_PRIMARY=stripe → Stripe portal (createBillingPortalSession) with
|
|
71
|
+
* Clerk portal as fallback when Stripe is unconfigured
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveBillingPortalUrl(env: Env, sub: string, returnUrl: string): Promise<string | null>;
|
|
74
|
+
export declare function getScopeForTier(tier: Tier): ScopeToken[];
|
|
75
|
+
export interface QuotaResult {
|
|
76
|
+
allowed: boolean;
|
|
77
|
+
remaining: number;
|
|
78
|
+
resetAt: string;
|
|
79
|
+
tier: Tier;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Returns current-month usage from KV hot counter.
|
|
83
|
+
* Enterprise users always get allowed=true / remaining=Infinity.
|
|
84
|
+
*/
|
|
85
|
+
export declare function checkCallQuota(env: Env, sub: string, tier: Tier): Promise<QuotaResult>;
|
|
86
|
+
export declare function recordUsage(env: Env, sub: string, tier: Tier): void;
|
|
87
|
+
/**
|
|
88
|
+
* Create a Stripe Billing Portal session URL for the given customer.
|
|
89
|
+
* Used by /oauth/billing route when BILLING_PRIMARY=stripe.
|
|
90
|
+
* Kept during the dual-write period; will be removed after Clerk-primary cutover.
|
|
91
|
+
*
|
|
92
|
+
* Returns null if Stripe is not configured or the user has no Stripe customer record.
|
|
93
|
+
*/
|
|
94
|
+
export declare function createBillingPortalSession(env: Env, sub: string, returnUrl: string): Promise<string | null>;
|
|
95
|
+
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAMtC,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAIjD,CAAC;AAEF,eAAO,MAAM,WAAW,oCAAoC,CAAC;AAE7D,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;AACjD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAIpD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,CAIlD,CAAC;AAMF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,CAG1D;AAMD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAatE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,IAAI,GAAG,SAAS,EAC3B,YAAY,EAAE,IAAI,GACjB,IAAI,CAEN;AAMD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM9E;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB;AAMD,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,EAAE,CAExD;AAMD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,WAAW,CAAC,CAuBtB;AAMD,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CA2BnE;AAyCD;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4BxB"}
|
package/dist/billing.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrier MCP — Billing v2.0 (Phase 15 — Clerk Billing GA Migration)
|
|
3
|
+
*
|
|
4
|
+
* Tier model:
|
|
5
|
+
* free — 5,000 tool calls/mo, read scope only, no Stripe product
|
|
6
|
+
* pro — 50,000 tool calls/mo, read+write+intelligence, $49/mo
|
|
7
|
+
* enterprise — unlimited tool calls, read+write+admin, $499/mo, custom rate limits, SSO
|
|
8
|
+
*
|
|
9
|
+
* Storage layout (CARRIER_USERS KV):
|
|
10
|
+
* key: user:<sub> value: UserRecord (tier field drives all gate checks)
|
|
11
|
+
* key: usage:<sub>:<yyyymm> value: JSON { calls: number } (TTL: 35 days)
|
|
12
|
+
*
|
|
13
|
+
* Analytics Engine (carrier_mcp_audit dataset) is the source of truth for usage
|
|
14
|
+
* rollups — KV counter is a fast hot path for quota checks.
|
|
15
|
+
*
|
|
16
|
+
* BILLING_PRIMARY feature flag (Doppler carrier/prd):
|
|
17
|
+
* "clerk" → Clerk Billing is authoritative; KV is a cache/fallback.
|
|
18
|
+
* Portal redirects to Clerk-hosted billing page.
|
|
19
|
+
* Stripe webhook still runs (dual-write period; kept for 1 billing cycle).
|
|
20
|
+
* "stripe" → (default) Direct Stripe path; Clerk plan claim is a session-layer overlay.
|
|
21
|
+
*
|
|
22
|
+
* IMPORTANT: This module NEVER charges a card. It only:
|
|
23
|
+
* 1. Gates tool calls based on tier + call count.
|
|
24
|
+
* 2. Records usage records to Stripe Metered Billing (for Pro overages, Stripe-primary only).
|
|
25
|
+
* 3. Redirects users to the appropriate Billing Portal (Clerk or Stripe).
|
|
26
|
+
*/
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Constants
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
export const TIER_CALL_LIMITS = {
|
|
31
|
+
free: 5_000,
|
|
32
|
+
pro: 50_000,
|
|
33
|
+
enterprise: Infinity,
|
|
34
|
+
};
|
|
35
|
+
export const UPGRADE_URL = "https://mcp.carrier.llc/upgrade";
|
|
36
|
+
// Intelligence scope is a virtual scope — Pro users can call intelligence tools
|
|
37
|
+
// because those tools are registered under "read" or "write" scope internally.
|
|
38
|
+
export const TIER_SCOPES = {
|
|
39
|
+
free: ["read"],
|
|
40
|
+
pro: ["read", "write"],
|
|
41
|
+
enterprise: ["read", "write", "admin"],
|
|
42
|
+
};
|
|
43
|
+
export function getBillingPrimary(env) {
|
|
44
|
+
const raw = env.BILLING_PRIMARY;
|
|
45
|
+
return raw === "clerk" ? "clerk" : "stripe";
|
|
46
|
+
}
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// getUserTier
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/**
|
|
51
|
+
* Read the tier stored in the user's KV record.
|
|
52
|
+
* Falls back to "free" if the record is missing or malformed.
|
|
53
|
+
* When BILLING_PRIMARY=clerk this becomes a cache/fallback only.
|
|
54
|
+
*/
|
|
55
|
+
export async function getUserTier(env, sub) {
|
|
56
|
+
const raw = await env.CARRIER_USERS.get(`user:${sub}`, "json").catch(() => null);
|
|
57
|
+
if (raw &&
|
|
58
|
+
typeof raw === "object" &&
|
|
59
|
+
"tier" in raw &&
|
|
60
|
+
raw.tier in TIER_CALL_LIMITS) {
|
|
61
|
+
return raw.tier;
|
|
62
|
+
}
|
|
63
|
+
return "free";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Phase 15 — Resolve the effective tier.
|
|
67
|
+
*
|
|
68
|
+
* BILLING_PRIMARY=clerk: Clerk session plan claim is primary; KV is the fallback.
|
|
69
|
+
* BILLING_PRIMARY=stripe (default): KV is canonical; Clerk plan is an overlay
|
|
70
|
+
* (used to catch Clerk-driven downgrades like plan cancellations).
|
|
71
|
+
*
|
|
72
|
+
* In both modes: clerkPlan ?? fallbackTier — the function is the same; the
|
|
73
|
+
* caller decides which value to pass as fallbackTier based on BILLING_PRIMARY.
|
|
74
|
+
*/
|
|
75
|
+
export function resolveTierFromClerk(clerkPlan, fallbackTier) {
|
|
76
|
+
return clerkPlan ?? fallbackTier;
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Portal URL resolution
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
/**
|
|
82
|
+
* Phase 15 — Build the Clerk-hosted billing portal URL.
|
|
83
|
+
* Activated as primary when BILLING_PRIMARY=clerk.
|
|
84
|
+
*/
|
|
85
|
+
export function buildClerkBillingPortalUrl(env, returnUrl) {
|
|
86
|
+
const base = env.CLERK_BILLING_PORTAL_URL ?? "https://accounts.carrier.llc/user/billing";
|
|
87
|
+
const url = new URL(base);
|
|
88
|
+
url.searchParams.set("redirect_url", returnUrl);
|
|
89
|
+
return url.toString();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Phase 15 — Resolve the billing portal URL based on BILLING_PRIMARY.
|
|
93
|
+
* This is the single entry point for all portal redirects (oauth/index.ts,
|
|
94
|
+
* console billing routes, etc.).
|
|
95
|
+
*
|
|
96
|
+
* BILLING_PRIMARY=clerk → Clerk-hosted portal (buildClerkBillingPortalUrl)
|
|
97
|
+
* BILLING_PRIMARY=stripe → Stripe portal (createBillingPortalSession) with
|
|
98
|
+
* Clerk portal as fallback when Stripe is unconfigured
|
|
99
|
+
*/
|
|
100
|
+
export async function resolveBillingPortalUrl(env, sub, returnUrl) {
|
|
101
|
+
const primary = getBillingPrimary(env);
|
|
102
|
+
if (primary === "clerk") {
|
|
103
|
+
return buildClerkBillingPortalUrl(env, returnUrl);
|
|
104
|
+
}
|
|
105
|
+
// BILLING_PRIMARY=stripe — try Stripe first, fall back to Clerk portal
|
|
106
|
+
const stripePortalUrl = await createBillingPortalSession(env, sub, returnUrl);
|
|
107
|
+
if (stripePortalUrl)
|
|
108
|
+
return stripePortalUrl;
|
|
109
|
+
return buildClerkBillingPortalUrl(env, returnUrl);
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// getScopeForTier
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
export function getScopeForTier(tier) {
|
|
115
|
+
return TIER_SCOPES[tier];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns current-month usage from KV hot counter.
|
|
119
|
+
* Enterprise users always get allowed=true / remaining=Infinity.
|
|
120
|
+
*/
|
|
121
|
+
export async function checkCallQuota(env, sub, tier) {
|
|
122
|
+
const limit = TIER_CALL_LIMITS[tier];
|
|
123
|
+
const resetAt = firstDayNextMonth();
|
|
124
|
+
if (tier === "enterprise") {
|
|
125
|
+
return { allowed: true, remaining: Infinity, resetAt, tier };
|
|
126
|
+
}
|
|
127
|
+
const month = currentMonth();
|
|
128
|
+
const usageKey = `usage:${sub}:${month}`;
|
|
129
|
+
const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
|
|
130
|
+
const calls = raw && typeof raw === "object" && "calls" in raw
|
|
131
|
+
? Number(raw.calls)
|
|
132
|
+
: 0;
|
|
133
|
+
const remaining = Math.max(0, limit - calls);
|
|
134
|
+
return {
|
|
135
|
+
allowed: calls < limit,
|
|
136
|
+
remaining,
|
|
137
|
+
resetAt,
|
|
138
|
+
tier,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// recordUsage
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
export function recordUsage(env, sub, tier) {
|
|
145
|
+
(async () => {
|
|
146
|
+
const month = currentMonth();
|
|
147
|
+
const usageKey = `usage:${sub}:${month}`;
|
|
148
|
+
const raw = await env.CARRIER_USERS.get(usageKey, "json").catch(() => null);
|
|
149
|
+
const prev = raw && typeof raw === "object" && "calls" in raw
|
|
150
|
+
? Number(raw.calls)
|
|
151
|
+
: 0;
|
|
152
|
+
const next = prev + 1;
|
|
153
|
+
await env.CARRIER_USERS.put(usageKey, JSON.stringify({ calls: next, updated_at: new Date().toISOString() }), { expirationTtl: 35 * 24 * 60 * 60 });
|
|
154
|
+
// Push Stripe usage record only when Stripe is the primary billing source.
|
|
155
|
+
// When BILLING_PRIMARY=clerk, Clerk Billing handles metering natively.
|
|
156
|
+
if (tier === "pro" && next % 100 === 0 && getBillingPrimary(env) === "stripe") {
|
|
157
|
+
await pushStripeUsageRecord(env, sub, 100).catch(() => {
|
|
158
|
+
// Non-fatal — nightly rollup will reconcile.
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
})();
|
|
162
|
+
}
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// pushStripeUsageRecord (internal — Stripe-primary mode only)
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
async function pushStripeUsageRecord(env, sub, quantity) {
|
|
167
|
+
const stripeKey = env
|
|
168
|
+
.STRIPE_SECRET_KEY;
|
|
169
|
+
if (!stripeKey)
|
|
170
|
+
return;
|
|
171
|
+
const subItemId = await env.CARRIER_USERS.get(`stripe_sub_item_id:${sub}`);
|
|
172
|
+
if (!subItemId)
|
|
173
|
+
return;
|
|
174
|
+
const body = new URLSearchParams({
|
|
175
|
+
quantity: String(quantity),
|
|
176
|
+
timestamp: String(Math.floor(Date.now() / 1000)),
|
|
177
|
+
action: "increment",
|
|
178
|
+
});
|
|
179
|
+
await fetch(`https://api.stripe.com/v1/subscription_items/${subItemId}/usage_records`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: {
|
|
182
|
+
Authorization: `Bearer ${stripeKey}`,
|
|
183
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
184
|
+
},
|
|
185
|
+
body: body.toString(),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// createBillingPortalSession (Stripe — kept for dual-write period)
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
/**
|
|
192
|
+
* Create a Stripe Billing Portal session URL for the given customer.
|
|
193
|
+
* Used by /oauth/billing route when BILLING_PRIMARY=stripe.
|
|
194
|
+
* Kept during the dual-write period; will be removed after Clerk-primary cutover.
|
|
195
|
+
*
|
|
196
|
+
* Returns null if Stripe is not configured or the user has no Stripe customer record.
|
|
197
|
+
*/
|
|
198
|
+
export async function createBillingPortalSession(env, sub, returnUrl) {
|
|
199
|
+
const stripeKey = env
|
|
200
|
+
.STRIPE_SECRET_KEY;
|
|
201
|
+
if (!stripeKey)
|
|
202
|
+
return null;
|
|
203
|
+
const customerId = await env.CARRIER_USERS.get(`stripe_customer_id:${sub}`);
|
|
204
|
+
if (!customerId)
|
|
205
|
+
return null;
|
|
206
|
+
const body = new URLSearchParams({
|
|
207
|
+
customer: customerId,
|
|
208
|
+
return_url: returnUrl,
|
|
209
|
+
});
|
|
210
|
+
const resp = await fetch("https://api.stripe.com/v1/billing_portal/sessions", {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: {
|
|
213
|
+
Authorization: `Bearer ${stripeKey}`,
|
|
214
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
215
|
+
},
|
|
216
|
+
body: body.toString(),
|
|
217
|
+
});
|
|
218
|
+
if (!resp.ok)
|
|
219
|
+
return null;
|
|
220
|
+
const data = (await resp.json());
|
|
221
|
+
return data.url ?? null;
|
|
222
|
+
}
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Helpers
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
function currentMonth() {
|
|
227
|
+
const now = new Date();
|
|
228
|
+
return `${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
229
|
+
}
|
|
230
|
+
function firstDayNextMonth() {
|
|
231
|
+
const now = new Date();
|
|
232
|
+
const y = now.getUTCFullYear();
|
|
233
|
+
const m = now.getUTCMonth() + 1;
|
|
234
|
+
if (m === 12) {
|
|
235
|
+
return new Date(Date.UTC(y + 1, 0, 1)).toISOString();
|
|
236
|
+
}
|
|
237
|
+
return new Date(Date.UTC(y, m, 1)).toISOString();
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"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"}
|