Files
calendar/packages/features/ee/billing/seed.ts
T
sean-brydonGitHubunknown <>unknown <>sean@cal.com <Sean@brydon.io>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
d52e2d9fad feat: active user billing (#27867)
* factory and statergie

* chore: use correct method of DI

* feat: add onchagne

* add logic to HWM stat

* add webhook resolver methods to each statergy

* move seat tracking + webhooks over to own statergy

* Move to factory base approach

* move logic to correct class

* rename create -> createByTeamId

* fix: remove debug `true ||` overrides from IS_STRIPE_ENABLED and IS_TEAM_BILLING_ENABLED

Remove accidentally committed debug overrides that short-circuited
IS_STRIPE_ENABLED and IS_TEAM_BILLING_ENABLED to always be true,
bypassing Stripe credential checks. This would break self-hosted
instances without Stripe configured.

Identified by cubic (https://cubic.dev)

Co-Authored-By: unknown <>

* feat: active user billing

* add tests

* UI side for users on active billing

* use correct period of stripe sub

* feat: claude feedback

* fix: skip Stripe sync for canceled/expired subscriptions to prevent repeated API calls

Co-Authored-By: unknown <>

* feat: feedback

* feat: only render when active users mode is set

* fix type error

* fix: constants + feature flags

* fix: default to null in tests

* chore: use seats in test

* fix: use node:crypto protocol for Node.js builtin imports

Co-Authored-By: sean@cal.com <Sean@brydon.io>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-12 13:57:32 +00:00

168 lines
4.7 KiB
TypeScript

#!/usr/bin/env npx tsx
/**
* Interactive seed script launcher for billing test data.
*
* Usage:
* npx tsx packages/features/ee/billing/seed.ts
*
* Non-interactive (CI-friendly):
* npx tsx packages/features/ee/billing/seed.ts --hwm
* npx tsx packages/features/ee/billing/seed.ts --proration
* npx tsx packages/features/ee/billing/seed.ts --active-user
* npx tsx packages/features/ee/billing/seed.ts --all
* npx tsx packages/features/ee/billing/seed.ts --cleanup
*
* Flags (combinable with above):
* --skip-stripe Skip Stripe API calls (use fake IDs)
* --cleanup Clean up before seeding
*/
import { spawn } from "node:child_process";
import * as readline from "node:readline";
const HWM_SCRIPT =
"packages/features/ee/billing/service/highWaterMark/seed-hwm-test.ts";
const PRORATION_SCRIPT =
"packages/features/ee/billing/service/dueInvoice/seed-proration-test.ts";
const ACTIVE_USER_SCRIPT =
"packages/features/ee/billing/active-user/seed-active-user-test.ts";
const passthrough = process.argv.filter((a) => a === "--skip-stripe");
function run(script: string, extraArgs: string[] = []): Promise<number> {
const args = ["tsx", script, ...passthrough, ...extraArgs];
return new Promise((resolve) => {
const child = spawn("npx", args, { stdio: "inherit", shell: true });
child.on("close", (code) => resolve(code ?? 1));
});
}
async function seedHwm(cleanup: boolean) {
console.log("\n--- Seeding High Water Mark test data ---\n");
const extra = cleanup ? ["--cleanup"] : [];
return run(HWM_SCRIPT, extra);
}
async function seedProration(cleanup: boolean) {
console.log("\n--- Seeding Proration test data ---\n");
const extra = cleanup ? ["--cleanup"] : [];
return run(PRORATION_SCRIPT, extra);
}
async function seedActiveUser(cleanup: boolean) {
console.log("\n--- Seeding Active User Billing test data ---\n");
const extra = cleanup ? ["--cleanup"] : [];
return run(ACTIVE_USER_SCRIPT, extra);
}
async function seedAll(cleanup: boolean) {
let code = await seedHwm(cleanup);
if (code !== 0) return code;
code = await seedProration(cleanup);
if (code !== 0) return code;
code = await seedActiveUser(cleanup);
return code;
}
async function cleanupAll() {
console.log("\n--- Cleaning up all billing test data ---\n");
let code = await run(HWM_SCRIPT, ["--cleanup", "--skip-stripe"]);
if (code !== 0) return code;
code = await run(PRORATION_SCRIPT, ["--cleanup", "--skip-stripe"]);
if (code !== 0) return code;
code = await run(ACTIVE_USER_SCRIPT, ["--cleanup", "--skip-stripe"]);
return code;
}
function prompt(question: string): Promise<string> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) => {
rl.question(question, (answer) => {
rl.close();
resolve(answer.trim());
});
});
}
async function interactive() {
console.log("=== Billing Test Data Seeder ===\n");
console.log(" 1) Seed HWM (High Water Mark) test data");
console.log(" 2) Seed Proration test data");
console.log(" 3) Seed Active User Billing test data");
console.log(" 4) Seed all");
console.log(" 5) Cleanup all test data");
console.log(" q) Quit\n");
const choice = await prompt("Choose [1-5, q]: ");
if (choice === "q" || choice === "") {
console.log("Bye.");
return;
}
let cleanup = false;
if (["1", "2", "3", "4"].includes(choice)) {
const ans = await prompt("Run cleanup before seeding? [y/N]: ");
cleanup = ans.toLowerCase() === "y";
}
let code = 0;
switch (choice) {
case "1":
code = await seedHwm(cleanup);
break;
case "2":
code = await seedProration(cleanup);
break;
case "3":
code = await seedActiveUser(cleanup);
break;
case "4":
code = await seedAll(cleanup);
break;
case "5":
code = await cleanupAll();
break;
default:
console.log(`Unknown option: ${choice}`);
code = 1;
}
process.exit(code);
}
async function main() {
const args = process.argv
.slice(2)
.filter((a) => !a.startsWith("--skip-stripe"));
if (args.includes("--hwm")) {
process.exit(await seedHwm(args.includes("--cleanup")));
}
if (args.includes("--proration")) {
process.exit(await seedProration(args.includes("--cleanup")));
}
if (args.includes("--active-user")) {
process.exit(await seedActiveUser(args.includes("--cleanup")));
}
if (args.includes("--all")) {
process.exit(await seedAll(args.includes("--cleanup")));
}
if (
args.includes("--cleanup") &&
!args.includes("--hwm") &&
!args.includes("--proration") &&
!args.includes("--active-user") &&
!args.includes("--all")
) {
process.exit(await cleanupAll());
}
await interactive();
}
main();