@arnaudjnn/billing-tools 0.61.0 → 0.62.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/usage.js ADDED
@@ -0,0 +1,110 @@
1
+ import { getBillingCustomerId } from "./billing.js";
2
+ import { resolveAllowance } from "./allowance.js";
3
+ import { getSeatType } from "./seats.js";
4
+ import { planModel } from "./plan-model.js";
5
+ import { stripeBalanceUsageLedger } from "./usage-ledger.js";
6
+ const pct = (used, size) => size <= 0 ? 100 : Math.min(100, Math.max(0, Math.round((used / size) * 100)));
7
+ function toWindow(l) {
8
+ return { ...l, percent: pct(l.used, l.size), resetsAt: l.window.end };
9
+ }
10
+ /** The cap's own window, so a screen can render pool/pack with the same row as a
11
+ * rate limit rather than special-casing three shapes. */
12
+ function capWindow(every, label, size, used, cycle) {
13
+ return {
14
+ every,
15
+ scope: "org",
16
+ label,
17
+ size,
18
+ used,
19
+ remaining: Math.max(0, size - used),
20
+ window: cycle,
21
+ percent: pct(used, size),
22
+ resetsAt: cycle.end,
23
+ };
24
+ }
25
+ /**
26
+ * Everything a usage screen shows for one subject (a workspace, or one member).
27
+ *
28
+ * One `resolveAllowance` underneath, so the numbers a screen shows and the numbers
29
+ * the meter enforces are the same numbers, read the same way. A screen that
30
+ * computed its own would eventually disagree with the gate — and the disagreement
31
+ * would be invisible until a customer was refused at 60%.
32
+ */
33
+ export async function usageSummary(adapter, config, input) {
34
+ const model = planModel(input.plans, input.plan ?? null);
35
+ const at = input.now ?? Date.now();
36
+ // A member's seat decides which pack and which seat-scoped limits apply, so
37
+ // resolve it here rather than making every page do it.
38
+ let caller = input.caller;
39
+ if (caller && !caller.seatType) {
40
+ const seatType = caller.kind === "api"
41
+ ? "api"
42
+ : ((caller.id && (await getSeatType(adapter, input.orgId, caller.id))) || "standard");
43
+ caller = { ...caller, seatType };
44
+ }
45
+ const state = await resolveAllowance(adapter, config, {
46
+ orgId: input.orgId,
47
+ plans: input.plans,
48
+ plan: input.plan,
49
+ caller,
50
+ ledger: input.ledger ?? stripeBalanceUsageLedger(),
51
+ now: at,
52
+ });
53
+ return {
54
+ plan: state.plan,
55
+ cycle: state.cycle,
56
+ windows: state.limits.map(toWindow),
57
+ pool: state.pool
58
+ ? capWindow("cycle", model?.display?.pooled?.title ?? null, state.pool.size, state.pool.used, state.cycle)
59
+ : null,
60
+ pack: state.pack
61
+ ? {
62
+ ...capWindow("cycle", null, state.pack.size + state.pack.extra, state.pack.used, state.cycle),
63
+ seatType: state.pack.seatType,
64
+ extra: state.pack.extra,
65
+ }
66
+ : null,
67
+ wallet: state.wallet,
68
+ at,
69
+ };
70
+ }
71
+ /**
72
+ * Per-member usage for an admin view.
73
+ *
74
+ * One summary per member, run in parallel: the ledger has no group-by, and adding
75
+ * one to the seam would mean either a Stripe meter dimension per member (there is
76
+ * no such thing) or paging every transaction in the window and bucketing it here.
77
+ * A workspace is tens of members, not thousands, and this is a page, not the hot
78
+ * path — but it IS N round trips, so a caller that renders it on every request
79
+ * should cache it.
80
+ */
81
+ export async function memberUsage(adapter, config, input) {
82
+ const at = input.now ?? Date.now();
83
+ const customerId = await getBillingCustomerId(adapter, input.orgId);
84
+ if (!customerId)
85
+ return [];
86
+ return Promise.all(input.members.map(async (m) => {
87
+ const kind = m.kind ?? "user";
88
+ const summary = await usageSummary(adapter, config, {
89
+ orgId: input.orgId,
90
+ plans: input.plans,
91
+ plan: input.plan,
92
+ caller: { kind, id: m.id },
93
+ ledger: input.ledger,
94
+ now: at,
95
+ });
96
+ // The pack is the per-member figure when the plan has one; without a pack
97
+ // (a pool or a pure wallet) fall back to the caller-scoped window, and
98
+ // failing that to zero, which is honest: a pooled plan does not attribute.
99
+ const usedInCycle = summary.pack?.used ?? summary.windows.find((w) => w.scope === "caller")?.used ?? 0;
100
+ return {
101
+ id: m.id,
102
+ kind,
103
+ seatType: summary.pack?.seatType ?? (kind === "api" ? "api" : "standard"),
104
+ pack: summary.pack ? { ...summary.pack, extra: summary.pack.extra } : null,
105
+ windows: summary.windows.filter((w) => w.scope === "caller"),
106
+ usedInCycle,
107
+ };
108
+ }));
109
+ }
110
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arnaudjnn/billing-tools",
3
- "version": "0.61.0",
3
+ "version": "0.62.0",
4
4
  "description": "The get-paid engine for Stripe + WorkOS apps, for humans and AI agents. Drop-in API-key auth, token/credit + subscription billing, auth.md agent registration, and MPP machine payments, as MCP tools, a REST API, and a CLI. Framework-agnostic, storage-pluggable.",
5
5
  "license": "MIT",
6
6
  "author": "Arnaud Jeannin",