// Hosted control centre: Stripe webhook signatures, sign-up, subscription lifecycle, provisioning tasks. // Temp data folders only; Stripe is simulated. import { test } from "node:test"; import assert from "node:assert/strict"; import crypto from "node:crypto"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "quorla-ctl-")); Object.assign(process.env, { QUORLA_CONTROL_DIR: path.join(TMP, "control"), QUORLA_DASHBOARD_DATA: path.join(TMP, "dash"), QUORLA_CONTROL: "true", STRIPE_SECRET_KEY: "sk_test_x", STRIPE_PRICE_ID: "price_x", STRIPE_WEBHOOK_SECRET: "whsec_test", PROVISION_TOKEN: "p".repeat(32), SMTP_HOST: "", }); const control = await import("../../dashboard/server/control.js"); const sign = (body, secret = "whsec_test", t = Math.floor(Date.now() / 1000)) => `t=${t},v1=${crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest("hex")}`; test("webhook signatures: valid accepted; wrong secret, old or missing rejected", () => { const body = JSON.stringify({ type: "ping" }); assert.equal(control.verifyWebhook(body, sign(body)).type, "ping"); assert.throws(() => control.verifyWebhook(body, sign(body, "whsec_other")), /doesn't match/); assert.throws(() => control.verifyWebhook(body, sign(body, "whsec_test", Math.floor(Date.now() / 1000) - 1000)), /too old/); assert.throws(() => control.verifyWebhook(body, ""), /Bad signature/); }); test("sign-up → Stripe Checkout → paid → provisioning → running → billing states", async () => { const realFetch = globalThis.fetch; let sent; globalThis.fetch = async (url, init) => { sent = { url: String(url), body: String(init.body) }; return new Response(JSON.stringify({ url: "https://checkout.stripe.test/s1" }), { status: 200 }); }; try { await assert.rejects(control.signup({ agencyName: "A", ownerEmail: "x", slug: "ab" }, "https://app.test"), /valid work email/); await assert.rejects(control.signup({ agencyName: "A", ownerEmail: "a@b.co", slug: "www" }, "https://app.test"), /Workspace address/); const r = await control.signup({ agencyName: "Northstar Digital", ownerName: "Ana", ownerEmail: "ana@northstar.test", slug: "northstar" }, "https://app.test"); assert.equal(r.url, "https://checkout.stripe.test/s1"); assert.match(sent.url, /checkout\/sessions$/); assert.match(decodeURIComponent(sent.body), /metadata\[slug\]=northstar/); } finally { globalThis.fetch = realFetch; } assert.equal(control.list()[0].status, "pending_payment"); assert.deepEqual(control.tasks(), [], "nothing to do before payment"); await control.onStripeEvent({ type: "checkout.session.completed", data: { object: { client_reference_id: "northstar", metadata: { slug: "northstar" }, customer: "cus_1", subscription: "sub_1" } } }); const [task] = control.tasks(); assert.equal(task.desired, "running"); assert.equal(task.port, 4400); assert.ok(task.setupToken); assert.throws(() => control.checkProvisionToken("Bearer nope"), /Not allowed/); control.checkProvisionToken(`Bearer ${"p".repeat(32)}`); const t = await control.report("northstar", { actual: "running" }); assert.equal(t.status, "active"); assert.equal(t.setupToken, undefined, "setup token never leaves the control centre in listings"); assert.deepEqual(control.tasks(), []); await control.onStripeEvent({ type: "invoice.payment_failed", data: { object: { subscription: "sub_1" } } }); assert.equal(control.list()[0].status, "past_due"); await control.onStripeEvent({ type: "customer.subscription.updated", data: { object: { id: "sub_1", status: "unpaid", metadata: { slug: "northstar" } } } }); assert.equal(control.list()[0].status, "suspended"); assert.equal(control.tasks()[0].desired, "stopped"); await control.report("northstar", { actual: "stopped" }); await control.onStripeEvent({ type: "customer.subscription.updated", data: { object: { id: "sub_1", status: "active", metadata: { slug: "northstar" } } } }); assert.equal(control.tasks()[0].desired, "running", "paying again restarts it"); });