@arnaudjnn/billing-tools 0.55.0 → 0.57.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/adapters/workos-org.d.ts +2 -0
- package/dist/adapters/workos-org.d.ts.map +1 -1
- package/dist/adapters/workos-org.js +4 -0
- package/dist/adapters/workos-org.js.map +1 -1
- package/dist/allowance.d.ts +81 -0
- package/dist/allowance.d.ts.map +1 -0
- package/dist/allowance.js +130 -0
- package/dist/allowance.js.map +1 -0
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +4 -0
- package/dist/billing.js.map +1 -1
- package/dist/doctor.d.ts +11 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +109 -0
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/metering.d.ts +38 -14
- package/dist/metering.d.ts.map +1 -1
- package/dist/metering.js +65 -42
- package/dist/metering.js.map +1 -1
- package/dist/plan-model.d.ts +395 -0
- package/dist/plan-model.d.ts.map +1 -0
- package/dist/plan-model.js +342 -0
- package/dist/plan-model.js.map +1 -0
- package/dist/plans.d.ts +25 -53
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +55 -34
- package/dist/plans.js.map +1 -1
- package/dist/pricing.d.ts +130 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +260 -0
- package/dist/pricing.js.map +1 -0
- package/dist/sync.d.ts +3 -16
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +45 -26
- package/dist/sync.js.map +1 -1
- package/dist/tax-ids.d.ts +26 -0
- package/dist/tax-ids.d.ts.map +1 -0
- package/dist/tax-ids.js +50 -0
- package/dist/tax-ids.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/tax-id-types.d.ts +10 -0
- package/dist/ui/tax-id-types.d.ts.map +1 -0
- package/dist/ui/tax-id-types.js +140 -0
- package/dist/ui/tax-id-types.js.map +1 -0
- package/dist/usage-ledger.d.ts +87 -0
- package/dist/usage-ledger.d.ts.map +1 -0
- package/dist/usage-ledger.js +117 -0
- package/dist/usage-ledger.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// What a plan IS, as data — and the normaliser that turns any accepted shape
|
|
2
|
+
// into one internal form.
|
|
3
|
+
//
|
|
4
|
+
// ZERO IMPORTS, deliberately. `plans.ts` imports `billing.ts` → `stripe`, so
|
|
5
|
+
// anything defined there can never be read by a browser bundle or a docs
|
|
6
|
+
// generator. The plan model has to be readable by both (a pricing card and a
|
|
7
|
+
// markdown table are the same derivation), so it lives here on its own, like
|
|
8
|
+
// `ui/limits.ts` does for the same reason.
|
|
9
|
+
//
|
|
10
|
+
// ── Why five axes instead of one ────────────────────────────────────────────
|
|
11
|
+
//
|
|
12
|
+
// A plan used to be `{ seats, tokensPerSeat, price, seatTypes?, allowanceMode? }`
|
|
13
|
+
// and that shape can only really express one product: per-seat, with a per-seat
|
|
14
|
+
// pack. Everything else was squeezed into `allowanceMode`, which despite its
|
|
15
|
+
// name does exactly ONE thing — skip the per-seat cap. There was no org-level
|
|
16
|
+
// allowance anywhere, so "we don't care about seats, here is a package of N tool
|
|
17
|
+
// requests" was unrepresentable, and a plan-level `tokensPerSeat` on such a plan
|
|
18
|
+
// was simply never read.
|
|
19
|
+
//
|
|
20
|
+
// What varies between products turns out to be five independent things:
|
|
21
|
+
//
|
|
22
|
+
// sells what Stripe charges for (nothing | seats | flat)
|
|
23
|
+
// grant what is CREDITED as money (none | purchased_seats | …)
|
|
24
|
+
// cap what is INCLUDED, as a limit (wallet | per_seat | pool)
|
|
25
|
+
// replenish how to get more (purchase | autoReload | request)
|
|
26
|
+
// sale whether it can be bought (free | self_serve | quote | legacy)
|
|
27
|
+
//
|
|
28
|
+
// Only `sells` is a discriminated union, because it alone decides which other
|
|
29
|
+
// fields are REQUIRED and what `ensurePlans` mints. Tagging all five under one
|
|
30
|
+
// `kind` would need a member per combination (`flat_pool_block_quote`), so the
|
|
31
|
+
// rest are plain fields with defaults derived from `sells`.
|
|
32
|
+
//
|
|
33
|
+
// ── Why `grant` and `cap` are different axes ────────────────────────────────
|
|
34
|
+
//
|
|
35
|
+
// This is the distinction the old model was missing, and it is a money bug, not
|
|
36
|
+
// a modelling preference. `grant` credits the Stripe customer balance. A Stripe
|
|
37
|
+
// credit balance AUTO-APPLIES to the customer's next invoice and cannot be
|
|
38
|
+
// opted out of — measured: granting 1000 tokens to a customer on a €21.04 seat
|
|
39
|
+
// produced an invoice with `starting_balance: -1000` and `amount_due: 1104`. So
|
|
40
|
+
// crediting a plan's "included" allowance discounts its own renewal by half, and
|
|
41
|
+
// an annual pool credited as money would invoice year two at zero.
|
|
42
|
+
//
|
|
43
|
+
// An included allowance is therefore a `cap`: a window that usage is COUNTED
|
|
44
|
+
// against, with no money moving. Credit stays what it is actually for —
|
|
45
|
+
// PURCHASED tokens (a top-up), which the customer really can spend.
|
|
46
|
+
export const INTERVALS = ["monthly", "yearly"];
|
|
47
|
+
export const isLegacyPlan = (d) => !("sells" in d);
|
|
48
|
+
/**
|
|
49
|
+
* Identity helper for a plans config.
|
|
50
|
+
*
|
|
51
|
+
* Annotating with `PlanCatalog` widens the keys to `string` and loses `display`
|
|
52
|
+
* autocomplete; this keeps the literal types while still checking the shape.
|
|
53
|
+
*/
|
|
54
|
+
export function definePlans(plans) {
|
|
55
|
+
return plans;
|
|
56
|
+
}
|
|
57
|
+
const hasPrice = (p) => p.monthly > 0 || p.yearly > 0;
|
|
58
|
+
function normalizeSeatTypes(seatTypes) {
|
|
59
|
+
return Object.entries(seatTypes).map(([key, s]) => {
|
|
60
|
+
const legacy = s;
|
|
61
|
+
const spec = s;
|
|
62
|
+
return {
|
|
63
|
+
key,
|
|
64
|
+
price: s.price,
|
|
65
|
+
includedTokens: s.includedTokens ?? 0,
|
|
66
|
+
min: spec.min ?? 0,
|
|
67
|
+
// `seats` was the legacy name for a per-type cap.
|
|
68
|
+
max: spec.max ?? legacy.seats ?? null,
|
|
69
|
+
shared: spec.shared ?? false,
|
|
70
|
+
display: spec.display ?? (legacy.label ? { label: legacy.label } : null),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Intervals a price actually exists for, so a plan can't advertise an interval
|
|
75
|
+
* Stripe has no price for. */
|
|
76
|
+
function soldIntervals(declared, price, seatTypes) {
|
|
77
|
+
if (declared)
|
|
78
|
+
return declared;
|
|
79
|
+
const has = (i) => (price ? price[i] > 0 : false) || seatTypes.some((s) => s.price[i] > 0);
|
|
80
|
+
return INTERVALS.filter(has);
|
|
81
|
+
}
|
|
82
|
+
export function normalizePlan(key, spec) {
|
|
83
|
+
if (!isLegacyPlan(spec)) {
|
|
84
|
+
const seatTypes = spec.sells.kind === "seats" ? normalizeSeatTypes(spec.sells.seatTypes) : [];
|
|
85
|
+
const price = spec.sells.kind === "flat" ? spec.sells.price : null;
|
|
86
|
+
const declared = spec.sells.kind === "nothing" ? [] : spec.sells.intervals;
|
|
87
|
+
return {
|
|
88
|
+
key,
|
|
89
|
+
sells: spec.sells,
|
|
90
|
+
grant: spec.grant ?? { kind: spec.sells.kind === "seats" ? "purchased_seats" : "none" },
|
|
91
|
+
cap: spec.cap ?? { kind: spec.sells.kind === "seats" ? "per_seat" : "wallet" },
|
|
92
|
+
replenish: spec.replenish ?? {},
|
|
93
|
+
sale: spec.sale,
|
|
94
|
+
limits: { members: spec.limits?.members ?? null },
|
|
95
|
+
display: spec.display ?? null,
|
|
96
|
+
intervals: soldIntervals(declared, price, seatTypes),
|
|
97
|
+
seatTypes,
|
|
98
|
+
price,
|
|
99
|
+
legacy: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Legacy. Every mapping here is behaviour-preserving; the property to hold is
|
|
103
|
+
// that no legacy config can produce `cap: "pool"`, so the new entitlement
|
|
104
|
+
// path stays dead until a config opts in.
|
|
105
|
+
const seatTypes = spec.seatTypes ? normalizeSeatTypes(spec.seatTypes) : [];
|
|
106
|
+
const sells = spec.seatTypes
|
|
107
|
+
? { kind: "seats", seatTypes: spec.seatTypes }
|
|
108
|
+
: hasPrice(spec.price)
|
|
109
|
+
? { kind: "flat", price: spec.price }
|
|
110
|
+
: { kind: "nothing" };
|
|
111
|
+
const grant = spec.seatTypes
|
|
112
|
+
? { kind: "purchased_seats" }
|
|
113
|
+
: hasPrice(spec.price)
|
|
114
|
+
? { kind: "per_member", tokens: spec.tokensPerSeat }
|
|
115
|
+
: { kind: "none" };
|
|
116
|
+
return {
|
|
117
|
+
key,
|
|
118
|
+
sells,
|
|
119
|
+
grant,
|
|
120
|
+
// "global" meant "no cap", full stop.
|
|
121
|
+
cap: spec.allowanceMode === "global"
|
|
122
|
+
? { kind: "wallet" }
|
|
123
|
+
: { kind: "per_seat", onExhausted: "block" },
|
|
124
|
+
replenish: {},
|
|
125
|
+
// Not knowable from the legacy shape, so it is guessed from whether ANY
|
|
126
|
+
// price exists — note a seat-typed plan whose every seat is 0 mints no
|
|
127
|
+
// Stripe price and therefore cannot be bought, which the old `price > 0`
|
|
128
|
+
// test on the plan-level amount got wrong in both directions. The doctor
|
|
129
|
+
// warns either way, because guessing this is what sold a quote-only plan.
|
|
130
|
+
sale: soldIntervals(undefined, sells.kind === "flat" ? spec.price : null, seatTypes).length === 0
|
|
131
|
+
? "free"
|
|
132
|
+
: "self_serve",
|
|
133
|
+
limits: { members: spec.seats },
|
|
134
|
+
display: null,
|
|
135
|
+
intervals: soldIntervals(undefined, sells.kind === "flat" ? spec.price : null, seatTypes),
|
|
136
|
+
seatTypes,
|
|
137
|
+
price: sells.kind === "flat" ? spec.price : null,
|
|
138
|
+
legacy: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function normalizePlans(plans) {
|
|
142
|
+
return Object.entries(plans).map(([key, spec]) => normalizePlan(key, spec));
|
|
143
|
+
}
|
|
144
|
+
export function planModel(plans, key) {
|
|
145
|
+
if (!key)
|
|
146
|
+
return null;
|
|
147
|
+
const spec = plans[key];
|
|
148
|
+
return spec ? normalizePlan(key, spec) : null;
|
|
149
|
+
}
|
|
150
|
+
/** Plan keys matching a predicate, e.g. `plansWhere(PLANS, p => p.sale === "self_serve")`.
|
|
151
|
+
* Replaces the several hand-maintained lists of "which plans can be bought". */
|
|
152
|
+
export function plansWhere(plans, predicate) {
|
|
153
|
+
return normalizePlans(plans)
|
|
154
|
+
.filter(predicate)
|
|
155
|
+
.map((m) => m.key);
|
|
156
|
+
}
|
|
157
|
+
/** Plans a customer can buy without talking to anyone. */
|
|
158
|
+
export const selfServePlans = (plans) => plansWhere(plans, (p) => p.sale === "self_serve" && p.sells.kind !== "nothing");
|
|
159
|
+
/** Where a seat stepper starts: each type's `min`, with the plan's `minSeats`
|
|
160
|
+
* absorbed by the first non-shared type so the default basket is already valid. */
|
|
161
|
+
export function defaultBasket(model) {
|
|
162
|
+
const basket = {};
|
|
163
|
+
for (const s of model.seatTypes)
|
|
164
|
+
basket[s.key] = s.min;
|
|
165
|
+
if (model.sells.kind !== "seats")
|
|
166
|
+
return basket;
|
|
167
|
+
const min = model.sells.minSeats ?? 0;
|
|
168
|
+
const total = Object.values(basket).reduce((a, b) => a + b, 0);
|
|
169
|
+
if (total < min) {
|
|
170
|
+
const first = model.seatTypes.find((s) => !s.shared) ?? model.seatTypes[0];
|
|
171
|
+
if (first)
|
|
172
|
+
basket[first.key] = (basket[first.key] ?? 0) + (min - total);
|
|
173
|
+
}
|
|
174
|
+
return basket;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Everything wrong with a basket, as data.
|
|
178
|
+
*
|
|
179
|
+
* Pure and network-free, so it can run in a stepper and at the checkout
|
|
180
|
+
* boundary from one implementation. `seats` and the per-type caps were declared
|
|
181
|
+
* for a long time and enforced nowhere: a crafted request could buy any
|
|
182
|
+
* quantity of any seat type, including fifty of a seat meant to be unique.
|
|
183
|
+
*/
|
|
184
|
+
export function validateBasket(plans, opts) {
|
|
185
|
+
const model = planModel(plans, opts.plan);
|
|
186
|
+
if (!model)
|
|
187
|
+
return [{ code: "unknown_plan" }];
|
|
188
|
+
const problems = [];
|
|
189
|
+
const purchasing = (opts.for ?? "purchase") === "purchase";
|
|
190
|
+
if (purchasing && model.sale !== "self_serve") {
|
|
191
|
+
problems.push({ code: "not_purchasable", sale: model.sale });
|
|
192
|
+
}
|
|
193
|
+
if (opts.interval && model.intervals.length && !model.intervals.includes(opts.interval)) {
|
|
194
|
+
problems.push({ code: "interval_unavailable", interval: opts.interval });
|
|
195
|
+
}
|
|
196
|
+
if (model.sells.kind !== "seats")
|
|
197
|
+
return problems;
|
|
198
|
+
const seats = opts.seats ?? {};
|
|
199
|
+
const known = new Map(model.seatTypes.map((s) => [s.key, s]));
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (const [key, qty] of Object.entries(seats)) {
|
|
202
|
+
if (qty <= 0)
|
|
203
|
+
continue;
|
|
204
|
+
const type = known.get(key);
|
|
205
|
+
if (!type) {
|
|
206
|
+
problems.push({ code: "unknown_seat_type", seatType: key });
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
total += qty;
|
|
210
|
+
if (type.max !== null && qty > type.max) {
|
|
211
|
+
problems.push({ code: "seat_type_limit", seatType: key, max: type.max, got: qty });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const min = model.sells.minSeats ?? 0;
|
|
215
|
+
if (total < min)
|
|
216
|
+
problems.push({ code: "below_minimum", min, got: total });
|
|
217
|
+
const max = model.sells.maxSeats ?? null;
|
|
218
|
+
if (max !== null && total > max)
|
|
219
|
+
problems.push({ code: "seat_limit", max, got: total });
|
|
220
|
+
const members = model.limits.members;
|
|
221
|
+
if (members !== null && total > members) {
|
|
222
|
+
problems.push({ code: "member_limit", max: members, got: total });
|
|
223
|
+
}
|
|
224
|
+
return problems;
|
|
225
|
+
}
|
|
226
|
+
/** One-line summary of a basket problem, for an error message. */
|
|
227
|
+
export function describeBasketProblem(p) {
|
|
228
|
+
switch (p.code) {
|
|
229
|
+
case "unknown_plan":
|
|
230
|
+
return "Unknown plan";
|
|
231
|
+
case "not_purchasable":
|
|
232
|
+
return `This plan is not self-serve (${p.sale})`;
|
|
233
|
+
case "unknown_seat_type":
|
|
234
|
+
return `Unknown seat type "${p.seatType}"`;
|
|
235
|
+
case "below_minimum":
|
|
236
|
+
return `At least ${p.min} seats are required (got ${p.got})`;
|
|
237
|
+
case "seat_limit":
|
|
238
|
+
return `At most ${p.max} seats (got ${p.got})`;
|
|
239
|
+
case "seat_type_limit":
|
|
240
|
+
return `At most ${p.max} "${p.seatType}" seat(s) (got ${p.got})`;
|
|
241
|
+
case "member_limit":
|
|
242
|
+
return `This plan allows at most ${p.max} members (got ${p.got})`;
|
|
243
|
+
case "interval_unavailable":
|
|
244
|
+
return `This plan is not sold ${p.interval}`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ── Allowance sizing ────────────────────────────────────────────────────────
|
|
248
|
+
/** Tokens to CREDIT for a paid cycle. Zero for `grant: none`, which is now the
|
|
249
|
+
* default for anything whose allowance is an entitlement. */
|
|
250
|
+
export function grantFor(model, ctx) {
|
|
251
|
+
if (!model)
|
|
252
|
+
return 0;
|
|
253
|
+
switch (model.grant.kind) {
|
|
254
|
+
case "none":
|
|
255
|
+
return 0;
|
|
256
|
+
case "fixed":
|
|
257
|
+
return model.grant.tokens;
|
|
258
|
+
case "per_member":
|
|
259
|
+
// The old flat path floored the count at 1 — a subscription with no
|
|
260
|
+
// recorded members still granted one seat's worth.
|
|
261
|
+
return model.grant.tokens * Math.max(1, ctx.memberCount ?? 0);
|
|
262
|
+
case "purchased_seats": {
|
|
263
|
+
const counts = ctx.seatCounts ?? {};
|
|
264
|
+
let sum = 0;
|
|
265
|
+
for (const s of model.seatTypes)
|
|
266
|
+
sum += s.includedTokens * (counts[s.key] ?? 0);
|
|
267
|
+
return sum;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** The org-wide entitlement for a cycle, or null when the plan has no pool.
|
|
272
|
+
* Falls back to the grant size when a plan both pools and credits. */
|
|
273
|
+
export function poolSizeOf(model) {
|
|
274
|
+
if (!model || model.cap.kind !== "pool")
|
|
275
|
+
return null;
|
|
276
|
+
if (model.cap.tokens != null)
|
|
277
|
+
return model.cap.tokens;
|
|
278
|
+
return model.grant.kind === "fixed" ? model.grant.tokens : 0;
|
|
279
|
+
}
|
|
280
|
+
/** The pack a caller is entitled to for the cycle, or null when uncapped. */
|
|
281
|
+
export function packSizeOf(model, seatType) {
|
|
282
|
+
if (!model || model.cap.kind !== "per_seat" || !seatType)
|
|
283
|
+
return null;
|
|
284
|
+
const type = model.seatTypes.find((s) => s.key === seatType);
|
|
285
|
+
return type ? type.includedTokens : null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* What to do when a window is used up.
|
|
289
|
+
*
|
|
290
|
+
* An agent's usage overflows into the wallet; a person's blocks. That was
|
|
291
|
+
* previously a hardcoded `caller.kind === "user"` test in the meter, so the
|
|
292
|
+
* caller kind is still honoured — a legacy config has no way to mark a seat type
|
|
293
|
+
* as shared, and changing behaviour for it silently is not on. A config that
|
|
294
|
+
* DOES declare `shared: true` gets the same answer without depending on how the
|
|
295
|
+
* call arrived.
|
|
296
|
+
*/
|
|
297
|
+
export function exhaustedPolicy(model, caller) {
|
|
298
|
+
if (!model)
|
|
299
|
+
return "wallet";
|
|
300
|
+
if (caller?.kind === "api")
|
|
301
|
+
return "wallet";
|
|
302
|
+
const type = caller?.seatType
|
|
303
|
+
? model.seatTypes.find((s) => s.key === caller.seatType)
|
|
304
|
+
: undefined;
|
|
305
|
+
if (type?.shared)
|
|
306
|
+
return "wallet";
|
|
307
|
+
if (model.cap.kind === "pool")
|
|
308
|
+
return model.cap.onExhausted ?? "block";
|
|
309
|
+
if (model.cap.kind === "per_seat")
|
|
310
|
+
return model.cap.onExhausted ?? "block";
|
|
311
|
+
return "wallet";
|
|
312
|
+
}
|
|
313
|
+
const startOfMonthUTC = (now) => {
|
|
314
|
+
const d = new Date(now);
|
|
315
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* The window usage is measured over.
|
|
319
|
+
*
|
|
320
|
+
* The SUBSCRIPTION period, when one is known — an annual pool measured over
|
|
321
|
+
* calendar months would reset twelve times a year, handing out twelve times the
|
|
322
|
+
* package. The calendar month remains the fallback for an org with no
|
|
323
|
+
* subscription (a free plan, or a pure wallet), which is what this library did
|
|
324
|
+
* unconditionally before.
|
|
325
|
+
*
|
|
326
|
+
* `rollover` widens the window to the subscription's start instead, which is all
|
|
327
|
+
* "unused allowance carries over" has to mean.
|
|
328
|
+
*/
|
|
329
|
+
export function cycleWindowFor(model, period, now = Date.now()) {
|
|
330
|
+
const ms = (v) => v == null ? null : typeof v === "number" ? v : Date.parse(v) || null;
|
|
331
|
+
const start = ms(period?.start);
|
|
332
|
+
const end = ms(period?.end);
|
|
333
|
+
const rollover = model?.cap.kind === "pool" && model.cap.rollover === true;
|
|
334
|
+
if (start && (rollover || start <= now)) {
|
|
335
|
+
const from = start;
|
|
336
|
+
const until = rollover ? null : end;
|
|
337
|
+
return { start: from, end: until, key: new Date(from).toISOString().slice(0, 10) };
|
|
338
|
+
}
|
|
339
|
+
const month = startOfMonthUTC(now);
|
|
340
|
+
return { start: month, end: null, key: new Date(month).toISOString().slice(0, 7) };
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=plan-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-model.js","sourceRoot":"","sources":["../src/plan-model.ts"],"names":[],"mappings":"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"}
|
package/dist/plans.d.ts
CHANGED
|
@@ -1,46 +1,7 @@
|
|
|
1
1
|
import type Stripe from "stripe";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* type). Introduced for the Claude-style Standard/Premium seat model. */
|
|
6
|
-
export interface SeatTypeDef {
|
|
7
|
-
/** Per-seat recurring price (cents). 0 = free (no Stripe price). */
|
|
8
|
-
price: {
|
|
9
|
-
monthly: number;
|
|
10
|
-
yearly: number;
|
|
11
|
-
};
|
|
12
|
-
/** Included tokens granted per seat of THIS type, per billing cycle. */
|
|
13
|
-
includedTokens: number;
|
|
14
|
-
/** Optional cap on seats of this type (null/undefined = unlimited). */
|
|
15
|
-
seats?: number | null;
|
|
16
|
-
/** Optional display label. */
|
|
17
|
-
label?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface PlanDef {
|
|
20
|
-
/** Max members per workspace. null = unlimited. */
|
|
21
|
-
seats: number | null;
|
|
22
|
-
/** Included tokens granted per seat, per billing cycle (flat model). */
|
|
23
|
-
tokensPerSeat: number;
|
|
24
|
-
/** Recurring price in the smallest currency unit (cents). 0 = free (no Stripe price). */
|
|
25
|
-
price: {
|
|
26
|
-
monthly: number;
|
|
27
|
-
yearly: number;
|
|
28
|
-
};
|
|
29
|
-
/** Optional multi-seat-type pricing. When present, `ensurePlans` mints one
|
|
30
|
-
* Stripe price per (plan, seatType, interval), the subscription carries one
|
|
31
|
-
* line item per seat type, and included tokens sum per type
|
|
32
|
-
* (`includedTokensByType`). When ABSENT the flat {seats, tokensPerSeat,
|
|
33
|
-
* price} model applies unchanged — existing consumers are unaffected. */
|
|
34
|
-
seatTypes?: Record<string, SeatTypeDef>;
|
|
35
|
-
/** How usage is metered against the plan's seats (default `per_seat`):
|
|
36
|
-
* • `per_seat` — each seat has its own per-cycle pack (user seats capped
|
|
37
|
-
* personally; the API seat is a shared pool + top-up).
|
|
38
|
-
* • `global` — pay per seat, but NO per-seat cap: all usage draws one
|
|
39
|
-
* committed workspace token pool (e.g. an Enterprise annual commitment). */
|
|
40
|
-
allowanceMode?: "per_seat" | "global";
|
|
41
|
-
}
|
|
42
|
-
export type PlansConfig = Record<string, PlanDef>;
|
|
43
|
-
export type BillingInterval = "monthly" | "yearly";
|
|
2
|
+
import { type BillingInterval, type PlanCatalog, type Sale, type SeatTypeDef } from "./plan-model.js";
|
|
3
|
+
export type { BillingInterval, Money, IntervalPrice, SeatTypeDef, SeatTypeSpec, SeatTypeDisplay, PlanDef, PlanSpec, PlanDisplay, PlanLimits, PlansConfig, PlanCatalog, PlanModel, NormalSeatType, Sells, Grant, Cap, Exhausted, Replenish, Sale, Quantities, BasketProblem, CycleWindow, } from "./plan-model.js";
|
|
4
|
+
export { definePlans, isLegacyPlan, normalizePlan, normalizePlans, planModel, plansWhere, selfServePlans, defaultBasket, validateBasket, describeBasketProblem, grantFor, poolSizeOf, packSizeOf, exhaustedPolicy, cycleWindowFor, } from "./plan-model.js";
|
|
44
5
|
export declare const DEFAULT_SEAT_TYPES: Record<string, SeatTypeDef>;
|
|
45
6
|
export declare const lookupKeyFor: (plan: string, interval: BillingInterval, seatType?: string) => string;
|
|
46
7
|
export interface EnsuredPrice {
|
|
@@ -56,7 +17,7 @@ export interface EnsuredPrice {
|
|
|
56
17
|
/** Idempotently create/reconcile Stripe products + prices for the paid plans.
|
|
57
18
|
* Returns the resolved price for every paid plan × interval. Free plans (both
|
|
58
19
|
* prices 0) create no Stripe objects. Safe to call on every boot / first use. */
|
|
59
|
-
export declare function ensurePlans(plans:
|
|
20
|
+
export declare function ensurePlans(plans: PlanCatalog, opts?: {
|
|
60
21
|
currency?: string;
|
|
61
22
|
taxBehavior?: Stripe.Price.TaxBehavior;
|
|
62
23
|
/**
|
|
@@ -115,7 +76,7 @@ export interface MigrateSubscriptionsResult {
|
|
|
115
76
|
* price attached by hand in the Dashboard is never moved out from under you.
|
|
116
77
|
*/
|
|
117
78
|
export declare function migrateSubscriptions(opts: {
|
|
118
|
-
plans:
|
|
79
|
+
plans: PlanCatalog;
|
|
119
80
|
plan: string;
|
|
120
81
|
interval: BillingInterval;
|
|
121
82
|
currency?: string;
|
|
@@ -136,7 +97,7 @@ export type PlanPrices = ReadonlyMap<string, string>;
|
|
|
136
97
|
*
|
|
137
98
|
* Look ids up with `lookupKeyFor(plan, interval, seatType)`.
|
|
138
99
|
*/
|
|
139
|
-
export declare function resolvePlanPrices(plans:
|
|
100
|
+
export declare function resolvePlanPrices(plans: PlanCatalog, opts?: {
|
|
140
101
|
currency?: string;
|
|
141
102
|
taxBehavior?: Stripe.Price.TaxBehavior;
|
|
142
103
|
}): Promise<PlanPrices>;
|
|
@@ -157,12 +118,23 @@ export declare function planForPriceId(priceId: string): Promise<string | null>;
|
|
|
157
118
|
* flat (non-seat-typed) price. */
|
|
158
119
|
export declare function seatTypeForPriceId(priceId: string): Promise<string | null>;
|
|
159
120
|
/** Seat limit for a plan (null = unlimited, undefined plan = null). */
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
121
|
+
/** Member limit for a plan (null = unlimited, unknown plan = null). */
|
|
122
|
+
export declare function seatLimit(plans: PlanCatalog, plan: string): number | null;
|
|
123
|
+
/** Per-type seat cap, or null when that type is unlimited. This is what makes a
|
|
124
|
+
* `max: 1` shared API seat mean something. */
|
|
125
|
+
export declare function seatTypeLimit(plans: PlanCatalog, plan: string, seatType: string): number | null;
|
|
126
|
+
/** How a plan is sold, which decides whether any checkout path may accept it. */
|
|
127
|
+
export declare function planSale(plans: PlanCatalog, plan: string): Sale | null;
|
|
128
|
+
/**
|
|
129
|
+
* Tokens to CREDIT for `seatCount` members on a plan, per cycle.
|
|
130
|
+
*
|
|
131
|
+
* Now expressed over `grant`, so a plan whose allowance is an ENTITLEMENT
|
|
132
|
+
* (`grant: none`, the default for everything but a credit-selling plan) returns
|
|
133
|
+
* 0 — crediting it would discount that plan's own invoice.
|
|
134
|
+
*/
|
|
135
|
+
export declare function includedTokens(plans: PlanCatalog, plan: string, seatCount: number): number;
|
|
136
|
+
/** Tokens to CREDIT given purchased counts per seat type. Falls back to the
|
|
137
|
+
* member-count form for plans without seat types, so callers can use it
|
|
138
|
+
* uniformly. */
|
|
139
|
+
export declare function includedTokensByType(plans: PlanCatalog, plan: string | null, counts: Record<string, number>): number;
|
|
168
140
|
//# sourceMappingURL=plans.d.ts.map
|
package/dist/plans.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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"}
|
package/dist/plans.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getStripe } from "./billing.js";
|
|
2
|
+
import { grantFor, INTERVALS, normalizePlans, planModel, } from "./plan-model.js";
|
|
3
|
+
export { definePlans, isLegacyPlan, normalizePlan, normalizePlans, planModel, plansWhere, selfServePlans, defaultBasket, validateBasket, describeBasketProblem, grantFor, poolSizeOf, packSizeOf, exhaustedPolicy, cycleWindowFor, } from "./plan-model.js";
|
|
2
4
|
// Library DEFAULT seat types, priced in USD (the lib's default currency — see
|
|
3
5
|
// ensurePlans `opts.currency ?? "usd"`). Consumers use these as-is or override
|
|
4
6
|
// (scartoffie, for instance, sets currency "eur" and its own EUR amounts).
|
|
@@ -10,7 +12,6 @@ export const DEFAULT_SEAT_TYPES = {
|
|
|
10
12
|
premium: { label: "Premium", price: { monthly: 12500, yearly: 120000 }, includedTokens: 5000 }, // $125/mo · $100/mo annually
|
|
11
13
|
api: { label: "API", price: { monthly: 62500, yearly: 600000 }, includedTokens: 25000, seats: 1 }, // ≈ 5× premium, one per workspace
|
|
12
14
|
};
|
|
13
|
-
const INTERVALS = ["monthly", "yearly"];
|
|
14
15
|
const STRIPE_INTERVAL = {
|
|
15
16
|
monthly: "month",
|
|
16
17
|
yearly: "year",
|
|
@@ -22,21 +23,34 @@ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
|
22
23
|
* intervals/seat types produce no Stripe object, so they're dropped here. */
|
|
23
24
|
function priceSpecs(plans) {
|
|
24
25
|
const specs = [];
|
|
25
|
-
for (const
|
|
26
|
+
for (const model of normalizePlans(plans)) {
|
|
26
27
|
const of = (interval, amount, seatType) => {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// A plan can decline an interval — an annual-only commitment declares
|
|
29
|
+
// `intervals: ["yearly"]` and no monthly price is ever minted for it.
|
|
30
|
+
if (amount > 0 && model.intervals.includes(interval)) {
|
|
31
|
+
specs.push({
|
|
32
|
+
plan: model.key,
|
|
33
|
+
interval,
|
|
34
|
+
seatType,
|
|
35
|
+
amount,
|
|
36
|
+
lookupKey: lookupKeyFor(model.key, interval, seatType),
|
|
37
|
+
});
|
|
29
38
|
}
|
|
30
39
|
};
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
switch (model.sells.kind) {
|
|
41
|
+
case "seats":
|
|
42
|
+
for (const seat of model.seatTypes) {
|
|
43
|
+
for (const interval of INTERVALS)
|
|
44
|
+
of(interval, seat.price[interval], seat.key);
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case "flat":
|
|
33
48
|
for (const interval of INTERVALS)
|
|
34
|
-
of(interval,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
of(interval, def.price[interval]);
|
|
49
|
+
of(interval, model.sells.price[interval]);
|
|
50
|
+
break;
|
|
51
|
+
case "nothing":
|
|
52
|
+
// Free, or a pure prepaid wallet: no Stripe object at all.
|
|
53
|
+
break;
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
return specs;
|
|
@@ -140,7 +154,9 @@ export async function ensurePlans(plans, opts = {}) {
|
|
|
140
154
|
let productId = productByPlan.get(plan);
|
|
141
155
|
if (!productId) {
|
|
142
156
|
const product = await stripe.products.create({
|
|
143
|
-
name
|
|
157
|
+
// The display name when the config has one, so the Stripe Dashboard and
|
|
158
|
+
// an invoice line read like the product rather than like its key.
|
|
159
|
+
name: planModel(plans, plan)?.display?.name ?? cap(plan),
|
|
144
160
|
metadata: { managedBy: MANAGED_BY, plan },
|
|
145
161
|
});
|
|
146
162
|
productId = product.id;
|
|
@@ -397,32 +413,37 @@ export async function seatTypeForPriceId(priceId) {
|
|
|
397
413
|
}
|
|
398
414
|
}
|
|
399
415
|
/** Seat limit for a plan (null = unlimited, undefined plan = null). */
|
|
416
|
+
/** Member limit for a plan (null = unlimited, unknown plan = null). */
|
|
400
417
|
export function seatLimit(plans, plan) {
|
|
401
|
-
return plans
|
|
418
|
+
return planModel(plans, plan)?.limits.members ?? null;
|
|
419
|
+
}
|
|
420
|
+
/** Per-type seat cap, or null when that type is unlimited. This is what makes a
|
|
421
|
+
* `max: 1` shared API seat mean something. */
|
|
422
|
+
export function seatTypeLimit(plans, plan, seatType) {
|
|
423
|
+
return planModel(plans, plan)?.seatTypes.find((s) => s.key === seatType)?.max ?? null;
|
|
402
424
|
}
|
|
403
|
-
/**
|
|
425
|
+
/** How a plan is sold, which decides whether any checkout path may accept it. */
|
|
426
|
+
export function planSale(plans, plan) {
|
|
427
|
+
return planModel(plans, plan)?.sale ?? null;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Tokens to CREDIT for `seatCount` members on a plan, per cycle.
|
|
431
|
+
*
|
|
432
|
+
* Now expressed over `grant`, so a plan whose allowance is an ENTITLEMENT
|
|
433
|
+
* (`grant: none`, the default for everything but a credit-selling plan) returns
|
|
434
|
+
* 0 — crediting it would discount that plan's own invoice.
|
|
435
|
+
*/
|
|
404
436
|
export function includedTokens(plans, plan, seatCount) {
|
|
405
|
-
|
|
406
|
-
if (!def)
|
|
407
|
-
return 0;
|
|
408
|
-
return def.tokensPerSeat * Math.max(1, seatCount);
|
|
437
|
+
return grantFor(planModel(plans, plan), { memberCount: seatCount });
|
|
409
438
|
}
|
|
410
|
-
/**
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* types, so callers can use it uniformly. */
|
|
439
|
+
/** Tokens to CREDIT given purchased counts per seat type. Falls back to the
|
|
440
|
+
* member-count form for plans without seat types, so callers can use it
|
|
441
|
+
* uniformly. */
|
|
414
442
|
export function includedTokensByType(plans, plan, counts) {
|
|
415
|
-
const
|
|
416
|
-
if (!
|
|
443
|
+
const model = planModel(plans, plan);
|
|
444
|
+
if (!model)
|
|
417
445
|
return 0;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return includedTokens(plans, plan, total);
|
|
421
|
-
}
|
|
422
|
-
let sum = 0;
|
|
423
|
-
for (const [type, st] of Object.entries(def.seatTypes)) {
|
|
424
|
-
sum += st.includedTokens * (counts[type] ?? 0);
|
|
425
|
-
}
|
|
426
|
-
return sum;
|
|
446
|
+
const total = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
447
|
+
return grantFor(model, { seatCounts: counts, memberCount: total });
|
|
427
448
|
}
|
|
428
449
|
//# sourceMappingURL=plans.js.map
|