@kernhq/module-billing 0.4.0 → 0.5.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.
@@ -0,0 +1,87 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ import { UpsertPlan } from '../../contract.js';
4
+ import * as plans from './plans.js';
5
+ import { paymentsEnabled } from './stripe.js';
6
+ /**
7
+ * Getting the plan catalogue onto an instance, and refusing to pretend one is there when it is not.
8
+ *
9
+ * Both halves exist for the same failure: a new workspace whose default plan is missing gets **no
10
+ * subscription row at all**, and no row resolves to *unlimited* — which is the right answer for
11
+ * every self-hosted Kern and exactly the wrong one for an instance that takes money. The only
12
+ * signal used to be a `warn` nobody reads, so a Kern Cloud one typo away from giving everything
13
+ * away looked completely healthy.
14
+ */
15
+ /** The file `KERN_PLANS_FILE` points at: a JSON array of plans, in the shape the admin console writes. */
16
+ const PlansFile = z.array(UpsertPlan);
17
+ /**
18
+ * Create any plan in `KERN_PLANS_FILE` whose slug this instance does not have yet.
19
+ *
20
+ * **Create, never update.** A plan is data an instance admin edits — prices, limits, what the
21
+ * pricing page says — and an image that rewrote the catalogue on every boot would silently revert
22
+ * their work at the next deploy. So a slug that already exists is left exactly as it is, and the
23
+ * file is only ever the answer to "a fresh instance has no plans at all".
24
+ *
25
+ * Returns the slugs it created, so the boot log says what happened rather than that something did.
26
+ */
27
+ export async function seedPlansFromFile(kernel) {
28
+ const path = process.env.KERN_PLANS_FILE;
29
+ if (!path)
30
+ return [];
31
+ let parsed;
32
+ try {
33
+ parsed = PlansFile.parse(JSON.parse(await readFile(path, 'utf8')));
34
+ }
35
+ catch (err) {
36
+ // Loud, and not fatal: an unreadable seed file must not take down a service that is otherwise
37
+ // configured, and the boot check below is what refuses when the result is actually unusable.
38
+ kernel.log.error({ err: String(err), path }, 'billing: KERN_PLANS_FILE could not be read as a list of plans; no plan was seeded');
39
+ return [];
40
+ }
41
+ const created = [];
42
+ for (const plan of parsed) {
43
+ if (await plans.bySlug(kernel, plan.slug))
44
+ continue;
45
+ // `id` is dropped: the file describes a catalogue, not this database's primary keys, and two
46
+ // instances seeded from one file must not be told they share a row.
47
+ const { id: _ignored, ...rest } = plan;
48
+ await plans.upsert(kernel, rest);
49
+ created.push(plan.slug);
50
+ }
51
+ if (created.length)
52
+ kernel.log.info({ created, path }, 'billing: seeded plans from KERN_PLANS_FILE');
53
+ return created;
54
+ }
55
+ /**
56
+ * Refuse to start, or say loudly why every new workspace is about to be unlimited.
57
+ *
58
+ * Two conditions, and they are not equally recoverable:
59
+ *
60
+ * - **No `KERN_DEFAULT_PLAN_SLUG` on an instance with a Stripe key.** Pure configuration, fixable
61
+ * in the environment without the service running, and it means every single signup is unlimited.
62
+ * That one throws.
63
+ * - **A slug naming a plan that does not exist.** Also wrong, and it is what a brand-new instance
64
+ * looks like for as long as it takes somebody to create the plan — so refusing here would lock
65
+ * the operator out of the console they need in order to fix it. That one is an `error` in the log
66
+ * and a refusal on the path that matters, which is `core.workspace.created`.
67
+ *
68
+ * An instance with no Stripe key is a self-hosted Kern: unlimited is the correct and intended
69
+ * answer there, and nothing here fires.
70
+ */
71
+ export async function assertDefaultPlan(kernel) {
72
+ if (!paymentsEnabled())
73
+ return;
74
+ const slug = process.env.KERN_DEFAULT_PLAN_SLUG;
75
+ if (!slug)
76
+ throw new Error('STRIPE_SECRET_KEY is set, so this instance sells subscriptions, but KERN_DEFAULT_PLAN_SLUG is ' +
77
+ 'empty. A workspace created without a plan has no subscription row, and no subscription row ' +
78
+ 'means unlimited seats, unlimited storage and SSO included — every signup would get the whole ' +
79
+ 'product for nothing.\n' +
80
+ 'Set KERN_DEFAULT_PLAN_SLUG to the slug of the plan new workspaces start on, or unset ' +
81
+ 'STRIPE_SECRET_KEY if this instance is not meant to take payments.');
82
+ if (!(await plans.bySlug(kernel, slug)))
83
+ kernel.log.error({ slug }, 'billing: KERN_DEFAULT_PLAN_SLUG names a plan that does not exist, so every new workspace ' +
84
+ 'will be created without a subscription and resolve to unlimited. Create the plan in the ' +
85
+ 'instance console, seed it with KERN_PLANS_FILE, or point the variable at a plan that exists.');
86
+ }
87
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","sourceRoot":"","sources":["../../../src/server/services/catalogue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;;;;;GAQG;AAEH,0GAA0G;AAC1G,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;AAErC;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAc;IACpD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,IAAI,MAAiC,CAAA;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,8FAA8F;QAC9F,6FAA6F;QAC7F,MAAM,CAAC,GAAG,CAAC,KAAK,CACd,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAC1B,mFAAmF,CACpF,CAAA;QACD,OAAO,EAAE,CAAA;IACX,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QACnD,6FAA6F;QAC7F,oEAAoE;QACpE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;QACtC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAChC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,4CAA4C,CAAC,CAAA;IACpG,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAc;IACpD,IAAI,CAAC,eAAe,EAAE;QAAE,OAAM;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAA;IAC/C,IAAI,CAAC,IAAI;QACP,MAAM,IAAI,KAAK,CACb,gGAAgG;YAC9F,6FAA6F;YAC7F,+FAA+F;YAC/F,wBAAwB;YACxB,uFAAuF;YACvF,mEAAmE,CACtE,CAAA;IACH,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,KAAK,CACd,EAAE,IAAI,EAAE,EACR,2FAA2F;YACzF,0FAA0F;YAC1F,8FAA8F,CACjG,CAAA;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../../src/server/services/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AA8BzD,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAgDhG"}
1
+ {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../../src/server/services/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAsDzD,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CA6ChG"}
@@ -11,6 +11,29 @@ import { overrides, plans, subscriptions } from '../schema.js';
11
11
  const ENTITLED = new Set(['trialing', 'active', 'past_due']);
12
12
  /** Statuses where the workspace may read but not write. */
13
13
  const INACTIVE = new Set(['suspended', 'canceled']);
14
+ /**
15
+ * What a workspace keeps once its subscription stops entitling it.
16
+ *
17
+ * This used to be `PlanLimits.parse({})`, which is *unlimited* — so cancelling or being suspended
18
+ * made a workspace strictly more capable than paying for it did, which is the opposite of what
19
+ * suspension means. Nothing noticed, because every field parsed and `active: false` reached only a
20
+ * banner.
21
+ *
22
+ * The rule now: never wider than the plan, and nothing left that lets the workspace **grow**. Seats
23
+ * and storage stop at what is already there, and SSO is a paid feature that stops being included.
24
+ *
25
+ * `apiRateLimit` and `auditRetentionDays` are deliberately untouched. Narrowing either would break
26
+ * *reading* — the budget is spent by every workspace-scoped request, retention decides what a
27
+ * pruner deletes — and being able to read and export what is yours is the one promise suspension
28
+ * makes. See ADR 0003 §6.
29
+ *
30
+ * `seats` cannot express a zero floor (`PlanLimits.seats` is positive-or-null, and 0 would fail the
31
+ * parse the plan screen does on the way out), so the floor is 1: no new member, and not one person
32
+ * removed from the ones already there.
33
+ */
34
+ function frozen(limits) {
35
+ return { ...limits, seats: limits.seats ?? 1, storageBytes: 0, sso: false };
36
+ }
14
37
  /**
15
38
  * What a workspace is allowed to do, resolved from its plan and any override an admin has set.
16
39
  *
@@ -52,15 +75,12 @@ export async function resolve(kernel, workspaceId) {
52
75
  // a default the admin never chose — which means dropping the keys the patch does not mention
53
76
  // rather than spreading them as `undefined`.
54
77
  const patch = ovr?.limits ? definedOnly(PlanLimitsPatch.parse(ovr.limits)) : null;
55
- const merged = patch
56
- ? { ...base, ...patch }
57
- : ENTITLED.has(row.status)
58
- ? base
59
- : // Not entitled: the plan's allowances do not apply, but nothing is taken away that would
60
- // stop the customer reading and exporting what is theirs.
61
- PlanLimits.parse({});
78
+ const merged = patch ? { ...base, ...patch } : base;
79
+ // The status decides entitlement; the plan and the override decide the numbers. An override is an
80
+ // operator comping a *limit*, never an operator un-suspending a workspace — that is `setStatus`,
81
+ // and keeping the two apart is what stops a comped account quietly outliving its own payment.
62
82
  return {
63
- ...merged,
83
+ ...(ENTITLED.has(row.status) ? merged : frozen(merged)),
64
84
  planName: row.planName ?? null,
65
85
  active: !INACTIVE.has(row.status),
66
86
  };
@@ -1 +1 @@
1
- {"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../../src/server/services/entitlements.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../../src/server/services/entitlements.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type Kernel } from '@kernhq/kernel';
2
2
  import Stripe from 'stripe';
3
+ import type { SubscriptionStatus } from '../../contract.js';
3
4
  /**
4
5
  * Stripe, or nothing at all.
5
6
  *
@@ -9,14 +10,45 @@ import Stripe from 'stripe';
9
10
  */
10
11
  export declare function client(): Stripe | null;
11
12
  export declare const paymentsEnabled: () => boolean;
13
+ /** Stripe's subscription statuses, mapped onto the five this module recognises. */
14
+ export declare function mapStatus(s: Stripe.Subscription.Status): SubscriptionStatus;
15
+ /**
16
+ * How many seats this workspace should be billed for right now.
17
+ *
18
+ * The count comes from core through the shared seat definition — guests never consume one — rather
19
+ * than from anything this module decides. It used to default to `1` whenever the caller did not
20
+ * pass a number, and no caller ever passes one, so a twelve-person workspace bought a single seat
21
+ * and stayed on it until the next membership event happened to fire `syncSeats`.
22
+ *
23
+ * Core being unreachable must not stop somebody paying us, so the stored counter is the fallback,
24
+ * and 1 is the floor — Stripe rejects a quantity of 0.
25
+ */
26
+ export declare function billableSeats(kernel: Kernel, workspaceId: string, asked?: number): Promise<number>;
27
+ /** Where Stripe sends the person back to, with `checkout=` for the screen to render. */
28
+ export declare function returnUrl(baseUrl: string, returnPath: string, outcome: string): string;
29
+ /**
30
+ * Put a workspace on a plan.
31
+ *
32
+ * **A workspace that already has a subscription is repriced, never sent through Checkout again.**
33
+ * Checkout in `subscription` mode creates a *new* subscription every time it completes, and
34
+ * `applySubscription` then overwrites `stripeSubscriptionId` — so an upgrade left the old
35
+ * subscription running with nothing pointing at it, and the customer was charged for both, for
36
+ * ever, while the pricing page promised "change any time; we bill the difference pro rata".
37
+ *
38
+ * Repricing is what keeps that promise: one subscription item moved to the new price with
39
+ * `create_prorations`, so Stripe credits the unused part of the old plan and charges the new one
40
+ * from today.
41
+ */
12
42
  export declare function checkout(kernel: Kernel, input: {
13
43
  workspaceId: string;
14
44
  planSlug: string;
15
45
  seats?: number;
16
46
  email?: string;
17
47
  baseUrl: string;
48
+ returnPath?: string;
18
49
  }): Promise<{
19
50
  url: string;
51
+ changed: boolean;
20
52
  }>;
21
53
  export declare function portal(kernel: Kernel, input: {
22
54
  workspaceId: string;
@@ -26,6 +58,8 @@ export declare function portal(kernel: Kernel, input: {
26
58
  }>;
27
59
  /** Keep the seat quantity on Stripe in step with the workspace's actual membership. */
28
60
  export declare function syncSeats(kernel: Kernel, workspaceId: string, seats: number): Promise<void>;
61
+ /** A grace clock `GRACE_DAYS` from now. */
62
+ export declare function graceFrom(now?: Date): Date;
29
63
  /**
30
64
  * Apply one webhook.
31
65
  *
@@ -1 +1 @@
1
- {"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../../../src/server/services/stripe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,MAAM,MAAM,QAAQ,CAAA;AAK3B;;;;;;GAMG;AACH,wBAAgB,MAAM,IAAI,MAAM,GAAG,IAAI,CAUtC;AAED,eAAO,MAAM,eAAe,eAA+C,CAAA;AAiD3E,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAuB1B;AAED,wBAAsB,MAAM,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAU1B;AAED,uFAAuF;AACvF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjG;AAkFD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAyD7C;AAED,gEAAgE;AAChE,eAAO,MAAM,UAAU,KAAK,CAAA"}
1
+ {"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../../../src/server/services/stripe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAK3D;;;;;;GAMG;AACH,wBAAgB,MAAM,IAAI,MAAM,GAAG,IAAI,CAYtC;AAqBD,eAAO,MAAM,eAAe,eAA+C,CAAA;AAY3E,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,kBAAkB,CA2B3E;AAyBD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxG;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IACL,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,GACA,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAgD5C;AAED,wBAAsB,MAAM,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAU1B;AAED,uFAAuF;AACvF,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjG;AAiDD,2CAA2C;AAC3C,wBAAgB,SAAS,CAAC,GAAG,OAAa,GAAG,IAAI,CAIhD;AAyFD;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B7C;AAsED,gEAAgE;AAChE,eAAO,MAAM,UAAU,KAAK,CAAA"}
@@ -3,6 +3,7 @@ import { eq } from 'drizzle-orm';
3
3
  import Stripe from 'stripe';
4
4
  import { invoices, plans, subscriptions, webhookEvents } from '../schema.js';
5
5
  import * as subs from './subscriptions.js';
6
+ import * as usage from './usage.js';
6
7
  /**
7
8
  * Stripe, or nothing at all.
8
9
  *
@@ -16,12 +17,33 @@ export function client() {
16
17
  return null;
17
18
  return new Stripe(key, {
18
19
  // Pinned deliberately: an account-level API version change must not reach a running instance
19
- // before its image has been built against it.
20
- apiVersion: '2026-07-29.dahlia',
20
+ // before its image has been built against it. This must match the version bundled with the
21
+ // installed `stripe` package — the SDK's types reject any other literal.
22
+ apiVersion: '2026-08-26.dahlia',
21
23
  appInfo: { name: 'Kern', url: 'https://kernaio.com' },
22
24
  maxNetworkRetries: 2,
25
+ ...apiBase(),
23
26
  });
24
27
  }
28
+ /**
29
+ * Where the Stripe API lives. `api.stripe.com` unless `STRIPE_API_BASE` says otherwise.
30
+ *
31
+ * It exists so that what this module *sends* Stripe can be asserted — that an upgrade repriced one
32
+ * subscription item instead of opening a second subscription, that the quantity was the workspace's
33
+ * real seat count, that the success URL is a route that exists. None of that is observable from the
34
+ * outside, and all three shipped wrong. Point it at `stripe-mock` or at a test double.
35
+ *
36
+ * Not a documented operator setting: an instance that sets this is not talking to Stripe, and the
37
+ * only honest reason to do that is a test.
38
+ */
39
+ function apiBase() {
40
+ const base = process.env.STRIPE_API_BASE;
41
+ if (!base)
42
+ return {};
43
+ const url = new URL(base);
44
+ const protocol = url.protocol === 'https:' ? 'https' : 'http';
45
+ return { host: url.hostname, port: Number(url.port || (protocol === 'https' ? 443 : 80)), protocol };
46
+ }
25
47
  export const paymentsEnabled = () => Boolean(process.env.STRIPE_SECRET_KEY);
26
48
  function required() {
27
49
  const s = client();
@@ -30,15 +52,23 @@ function required() {
30
52
  return s;
31
53
  }
32
54
  /** Stripe's subscription statuses, mapped onto the five this module recognises. */
33
- function mapStatus(s) {
55
+ export function mapStatus(s) {
34
56
  switch (s) {
35
57
  case 'trialing':
36
58
  return 'trialing';
37
59
  case 'active':
38
60
  return 'active';
39
61
  case 'past_due':
40
- case 'unpaid':
41
62
  return 'past_due';
63
+ /**
64
+ * `unpaid` is Stripe saying it has finished trying: the dunning window is over and it will not
65
+ * attempt the card again. It used to map to `past_due`, which is an *entitled* status here — and
66
+ * only the grace clock ends it, which nothing sets on this path. So the workspace Stripe had
67
+ * given up on stayed entitled for ever. It is suspended: read-only, resumable the moment a
68
+ * payment succeeds, and nothing deleted.
69
+ */
70
+ case 'unpaid':
71
+ return 'suspended';
42
72
  case 'canceled':
43
73
  case 'incomplete_expired':
44
74
  return 'canceled';
@@ -65,6 +95,53 @@ async function customerFor(kernel, workspaceId, email) {
65
95
  await subs.upsert(kernel, workspaceId, { stripeCustomerId: customer.id });
66
96
  return customer.id;
67
97
  }
98
+ /**
99
+ * Stripe subscription states an existing subscription can be repriced in.
100
+ *
101
+ * Everything else — cancelled, expired, given up on, never completed — is not a subscription to
102
+ * move; it is one to replace, which means Checkout and a fresh card.
103
+ */
104
+ const REPRICEABLE = new Set(['active', 'trialing', 'past_due']);
105
+ /**
106
+ * How many seats this workspace should be billed for right now.
107
+ *
108
+ * The count comes from core through the shared seat definition — guests never consume one — rather
109
+ * than from anything this module decides. It used to default to `1` whenever the caller did not
110
+ * pass a number, and no caller ever passes one, so a twelve-person workspace bought a single seat
111
+ * and stayed on it until the next membership event happened to fire `syncSeats`.
112
+ *
113
+ * Core being unreachable must not stop somebody paying us, so the stored counter is the fallback,
114
+ * and 1 is the floor — Stripe rejects a quantity of 0.
115
+ */
116
+ export async function billableSeats(kernel, workspaceId, asked) {
117
+ if (asked !== undefined)
118
+ return Math.max(1, asked);
119
+ try {
120
+ return Math.max(1, await usage.recountSeats(kernel, workspaceId));
121
+ }
122
+ catch (err) {
123
+ kernel.log.warn({ err: String(err), workspaceId }, 'billing: could not recount seats for checkout; using the stored counter');
124
+ return Math.max(1, (await usage.read(kernel, workspaceId)).seats);
125
+ }
126
+ }
127
+ /** Where Stripe sends the person back to, with `checkout=` for the screen to render. */
128
+ export function returnUrl(baseUrl, returnPath, outcome) {
129
+ const url = `${baseUrl.replace(/\/$/, '')}${returnPath.startsWith('/') ? returnPath : `/${returnPath}`}`;
130
+ return `${url}${url.includes('?') ? '&' : '?'}checkout=${outcome}`;
131
+ }
132
+ /**
133
+ * Put a workspace on a plan.
134
+ *
135
+ * **A workspace that already has a subscription is repriced, never sent through Checkout again.**
136
+ * Checkout in `subscription` mode creates a *new* subscription every time it completes, and
137
+ * `applySubscription` then overwrites `stripeSubscriptionId` — so an upgrade left the old
138
+ * subscription running with nothing pointing at it, and the customer was charged for both, for
139
+ * ever, while the pricing page promised "change any time; we bill the difference pro rata".
140
+ *
141
+ * Repricing is what keeps that promise: one subscription item moved to the new price with
142
+ * `create_prorations`, so Stripe credits the unused part of the old plan and charges the new one
143
+ * from today.
144
+ */
68
145
  export async function checkout(kernel, input) {
69
146
  const stripe = required();
70
147
  const [plan] = await kernel.database.db.select().from(plans).where(eq(plans.slug, input.planSlug)).limit(1);
@@ -72,23 +149,46 @@ export async function checkout(kernel, input) {
72
149
  throw KernError.notFound('Plan');
73
150
  if (!plan.stripePriceId)
74
151
  throw KernError.conflict('That plan has no Stripe price attached', 'billing.plan.no_price');
152
+ const quantity = plan.perSeat ? await billableSeats(kernel, input.workspaceId, input.seats) : 1;
153
+ const back = input.returnPath ?? '/';
154
+ const existing = await subs.get(kernel, input.workspaceId);
155
+ if (existing?.stripeSubscriptionId) {
156
+ const remote = await stripe.subscriptions.retrieve(existing.stripeSubscriptionId).catch(() => null);
157
+ const item = remote?.items.data[0];
158
+ if (remote && item && REPRICEABLE.has(remote.status)) {
159
+ const updated = await stripe.subscriptions.update(remote.id, {
160
+ items: [{ id: item.id, price: plan.stripePriceId, quantity }],
161
+ // the customer is billed the difference from today, not from the next period
162
+ proration_behavior: 'create_prorations',
163
+ // choosing a plan is a decision to keep the subscription, so a pending cancellation is off
164
+ cancel_at_period_end: false,
165
+ // the metadata is how a webhook finds its way home, and `kern_plan_id` is how the row
166
+ // learns which plan it is on — losing either here would strand the subscription
167
+ metadata: { kern_workspace_id: input.workspaceId, kern_plan_id: plan.id },
168
+ });
169
+ // Applied now rather than waiting for `customer.subscription.updated`: the person is looking
170
+ // at the screen. The webhook arrives too and writes the same thing, which is why it is safe.
171
+ await applySubscription(kernel, updated);
172
+ return { url: returnUrl(input.baseUrl, back, 'changed'), changed: true };
173
+ }
174
+ }
75
175
  const customer = await customerFor(kernel, input.workspaceId, input.email);
76
176
  const session = await stripe.checkout.sessions.create({
77
177
  mode: 'subscription',
78
178
  customer,
79
- line_items: [{ price: plan.stripePriceId, quantity: plan.perSeat ? (input.seats ?? 1) : 1 }],
179
+ line_items: [{ price: plan.stripePriceId, quantity }],
80
180
  subscription_data: {
81
181
  ...(plan.trialDays > 0 ? { trial_period_days: plan.trialDays } : {}),
82
182
  metadata: { kern_workspace_id: input.workspaceId, kern_plan_id: plan.id },
83
183
  },
84
184
  // The card is taken up front, trial or not, so the subscription converts without asking again.
85
185
  payment_method_collection: 'always',
86
- success_url: `${input.baseUrl}/settings/billing?checkout=done`,
87
- cancel_url: `${input.baseUrl}/settings/billing?checkout=cancelled`,
186
+ success_url: returnUrl(input.baseUrl, back, 'done'),
187
+ cancel_url: returnUrl(input.baseUrl, back, 'cancelled'),
88
188
  });
89
189
  if (!session.url)
90
190
  throw KernError.conflict('Stripe did not return a checkout URL', 'billing.stripe.no_url');
91
- return { url: session.url };
191
+ return { url: session.url, changed: false };
92
192
  }
93
193
  export async function portal(kernel, input) {
94
194
  const stripe = required();
@@ -134,10 +234,52 @@ async function claim(kernel, id, type) {
134
234
  .returning({ id: webhookEvents.id });
135
235
  return rows.length > 0;
136
236
  }
237
+ /**
238
+ * Give the claim back, so Stripe's retry is handled rather than answered "already applied".
239
+ *
240
+ * The claim is taken *before* the handler runs, because taking it after leaves two concurrent
241
+ * deliveries both applying the event. That ordering had a worse failure of its own: a handler that
242
+ * threw left the id claimed for ever, so every retry Stripe sent was recognised as a duplicate and
243
+ * dropped — the event was lost, permanently and silently, and a paid invoice was never mirrored.
244
+ *
245
+ * The two are not both fixable by one insert, so the claim is released on failure instead of being
246
+ * held in a transaction with the work. A transaction is the tidier answer and cannot be had here:
247
+ * `applyInvoice` runs inside `withWorkspace`, which takes its own connection to set the RLS context,
248
+ * so the insert and the write are on different connections by construction. The window this leaves
249
+ * — a duplicate delivery arriving while the first is failing — ends in the duplicate being dropped,
250
+ * which is what would have happened anyway, and Stripe retries either way.
251
+ */
252
+ async function release(kernel, id) {
253
+ await kernel.database.db
254
+ .delete(webhookEvents)
255
+ .where(eq(webhookEvents.id, id))
256
+ .catch((err) => kernel.log.error({ err: String(err), event: id }, 'billing: could not release a webhook claim; Stripe will treat its retry as a duplicate'));
257
+ }
137
258
  /** The workspace a Stripe object belongs to, from the metadata we set when creating it. */
138
259
  function workspaceOf(o) {
139
260
  return o.metadata?.kern_workspace_id ?? null;
140
261
  }
262
+ /** A grace clock `GRACE_DAYS` from now. */
263
+ export function graceFrom(now = new Date()) {
264
+ const grace = new Date(now);
265
+ grace.setUTCDate(grace.getUTCDate() + GRACE_DAYS);
266
+ return grace;
267
+ }
268
+ /**
269
+ * Write what Stripe says about a subscription into our row.
270
+ *
271
+ * The grace clock is the delicate part. This used to write `graceEndsAt: null` unconditionally, and
272
+ * Stripe emits `customer.subscription.updated` for the same failed payment as
273
+ * `invoice.payment_failed` — in no guaranteed order. So the update wiped the clock the invoice
274
+ * event had just set, `billing.close-grace-periods` requires a non-null clock to act, and the
275
+ * `past_due → suspended` transition therefore almost never fired: a workspace that stopped paying
276
+ * kept working indefinitely.
277
+ *
278
+ * The clock now belongs to the *status*, not to the event that happened to arrive last. Past due
279
+ * keeps a clock already running and starts one if there is none — which also covers the order where
280
+ * `subscription.updated` is the only thing that arrives. Any other status means the payment problem
281
+ * is over, and clears it.
282
+ */
141
283
  async function applySubscription(kernel, s) {
142
284
  const workspaceId = workspaceOf(s);
143
285
  if (!workspaceId) {
@@ -147,16 +289,26 @@ async function applySubscription(kernel, s) {
147
289
  const planId = s.metadata?.kern_plan_id ?? null;
148
290
  const item = s.items.data[0];
149
291
  const periodEnd = item?.current_period_end ?? null;
292
+ const status = mapStatus(s.status);
293
+ let graceEndsAt = null;
294
+ if (status === 'past_due') {
295
+ const [row] = await kernel.database.db
296
+ .select({ graceEndsAt: subscriptions.graceEndsAt })
297
+ .from(subscriptions)
298
+ .where(eq(subscriptions.workspaceId, workspaceId))
299
+ .limit(1);
300
+ graceEndsAt = row?.graceEndsAt ?? graceFrom();
301
+ }
150
302
  await subs.upsert(kernel, workspaceId, {
151
303
  ...(planId ? { planId } : {}),
152
- status: mapStatus(s.status),
304
+ status,
153
305
  seatsPurchased: item?.quantity ?? 0,
154
306
  stripeSubscriptionId: s.id,
155
307
  stripeCustomerId: typeof s.customer === 'string' ? s.customer : s.customer.id,
156
308
  cancelAtPeriodEnd: s.cancel_at_period_end,
157
309
  trialEndsAt: s.trial_end ? new Date(s.trial_end * 1000) : null,
158
310
  currentPeriodEnd: periodEnd ? new Date(periodEnd * 1000) : null,
159
- graceEndsAt: null,
311
+ graceEndsAt,
160
312
  });
161
313
  }
162
314
  async function applyInvoice(kernel, inv) {
@@ -221,6 +373,18 @@ export async function handleWebhook(kernel, raw, signature) {
221
373
  kernel.log.info({ event: event.id, type: event.type }, 'billing: webhook already applied');
222
374
  return { handled: false, type: event.type };
223
375
  }
376
+ try {
377
+ await apply(kernel, stripe, event);
378
+ }
379
+ catch (err) {
380
+ // The claim goes back so Stripe's retry is a real delivery rather than a duplicate. Rethrowing
381
+ // is what turns the retry on: the route answers 5xx and Stripe sends the event again.
382
+ await release(kernel, event.id);
383
+ throw err;
384
+ }
385
+ return { handled: true, type: event.type };
386
+ }
387
+ async function apply(kernel, stripe, event) {
224
388
  switch (event.type) {
225
389
  case 'customer.subscription.created':
226
390
  case 'customer.subscription.updated':
@@ -230,8 +394,30 @@ export async function handleWebhook(kernel, raw, signature) {
230
394
  case 'checkout.session.completed': {
231
395
  const session = event.data.object;
232
396
  const subId = typeof session.subscription === 'string' ? session.subscription : session.subscription?.id;
233
- if (subId)
234
- await applySubscription(kernel, await stripe.subscriptions.retrieve(subId));
397
+ if (!subId)
398
+ break;
399
+ const remote = await stripe.subscriptions.retrieve(subId);
400
+ await applySubscription(kernel, remote);
401
+ /**
402
+ * The quantity was taken from the seat count at the moment the session was opened, and
403
+ * somebody may have joined or left while the card was being typed. `syncSeats` is a no-op when
404
+ * the two already agree, so this costs one read on the normal path.
405
+ *
406
+ * The whole thing is inside the try, `await billableSeats` included: written as an argument to
407
+ * a `.catch()`ed call it would be evaluated *before* the handler is attached, so a failure
408
+ * counting seats would escape, fail the webhook, and cost us the record of a payment that has
409
+ * already happened. A wrong seat count is a billing correction the nightly reconcile makes; a
410
+ * lost `checkout.session.completed` is a customer who paid and got nothing.
411
+ */
412
+ const workspaceId = workspaceOf(remote);
413
+ if (workspaceId) {
414
+ try {
415
+ await syncSeats(kernel, workspaceId, await billableSeats(kernel, workspaceId));
416
+ }
417
+ catch (err) {
418
+ kernel.log.warn({ err: String(err), workspaceId }, 'billing: could not sync seats after checkout; the nightly reconcile will correct it');
419
+ }
420
+ }
235
421
  break;
236
422
  }
237
423
  case 'invoice.paid':
@@ -243,16 +429,19 @@ export async function handleWebhook(kernel, raw, signature) {
243
429
  const customer = typeof inv.customer === 'string' ? inv.customer : inv.customer?.id;
244
430
  if (customer) {
245
431
  const [row] = await kernel.database.db
246
- .select({ workspaceId: subscriptions.workspaceId })
432
+ .select({ workspaceId: subscriptions.workspaceId, graceEndsAt: subscriptions.graceEndsAt })
247
433
  .from(subscriptions)
248
434
  .where(eq(subscriptions.stripeCustomerId, customer))
249
435
  .limit(1);
250
436
  if (row) {
251
437
  // A failed payment starts a clock, it does not close the workspace. Stripe keeps retrying
252
438
  // for its own dunning window; the grace period is what decides when we stop waiting.
253
- const grace = new Date();
254
- grace.setUTCDate(grace.getUTCDate() + GRACE_DAYS);
255
- await subs.upsert(kernel, row.workspaceId, { status: 'past_due', graceEndsAt: grace });
439
+ // A clock already running is kept: a second failed invoice inside the window must not
440
+ // hand the customer another fourteen days.
441
+ await subs.upsert(kernel, row.workspaceId, {
442
+ status: 'past_due',
443
+ graceEndsAt: row.graceEndsAt ?? graceFrom(),
444
+ });
256
445
  }
257
446
  }
258
447
  break;
@@ -260,7 +449,6 @@ export async function handleWebhook(kernel, raw, signature) {
260
449
  default:
261
450
  kernel.log.debug({ type: event.type }, 'billing: unhandled Stripe event');
262
451
  }
263
- return { handled: true, type: event.type };
264
452
  }
265
453
  /** How long a workspace keeps working after a payment fails. */
266
454
  export const GRACE_DAYS = 14;
@@ -1 +1 @@
1
- {"version":3,"file":"stripe.js","sourceRoot":"","sources":["../../../src/server/services/stripe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stripe.js","sourceRoot":"","sources":["../../../src/server/services/stripe.ts"],"names":[],"mappings":"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"}