Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

561 linesCodeBlameRaw
1// Explicit sandbox-only integration checks against a locally running billing
2// Worker and isolated D1. Never accepts a live Stripe key or a remote Worker.
3import assert from "node:assert/strict";
4import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
5import { resolve } from "node:path";
6import { spawnSync } from "node:child_process";
7import { createHmac } from "node:crypto";
8
9const dir = resolve("target/billing-audit");
10mkdirSync(dir, { recursive: true });
11const key = readFileSync(".secrets/stripe-test-key.txt", "utf8").trim();
12assert.ok(/^(sk|rk)_test_/.test(key), "Expected a test API key");
13const version = readFileSync("services/billing/src/stripe.rs", "utf8").match(/STRIPE_VERSION: &str = "([^"]+)"/)[1];
14const endpoint = "http://127.0.0.1:8797";
15const secret = "whsec_local_billing_audit_only";
16const fixturePath = resolve(dir, "fixture.json");
17const phase = process.argv[2];
18assert.ok(
19 ["seed", "run", "lifecycle", "enterprise", "protocol", "complimentary", "cleanup"].includes(phase),
20 "Use seed, run, lifecycle, enterprise, protocol, complimentary, or cleanup",
21);
22// A local Worker is not enough isolation: production replays all events in
23// its Stripe account. The test and live keys below were verified to share
24// this account during the audit. Never seed or run fixtures there again.
25const productionStripeAccount = "acct_1UMzU3CgDtq6trU5";
26const auditAccount = await stripe("/account");
27assert.match(auditAccount.id, /^acct_/);
28if (phase !== "cleanup") {
29 assert.notEqual(
30 auditAccount.id,
31 productionStripeAccount,
32 "Use a separate Stripe Sandbox: this account is connected to production event replay. No test objects were created.",
33 );
34}
35async function stripe(path, fields, method = fields ? "POST" : "GET", apiVersion = version) {
36 const response = await fetch(`https://api.stripe.com/v1${path}`, {
37 method,
38 headers: { Authorization: `Bearer ${key}`, "Stripe-Version": apiVersion, ...(fields ? { "Content-Type": "application/x-www-form-urlencoded" } : {}) },
39 body: fields ? new URLSearchParams(fields) : undefined,
40 });
41 const result = await response.json();
42 if (!response.ok) throw Error(`Stripe ${method} ${path}: ${response.status} ${result.error?.code ?? result.error?.type ?? "error"}`);
43 assert.notEqual(result.livemode, true, "Sandbox returned a live object");
44 return result;
45}
46async function rpc(method, args) {
47 const response = await fetch(`${endpoint}/rpc/${method}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(args) });
48 if (!response.ok) throw Error(`Local billing ${method}: HTTP ${response.status}: ${(await response.text()).slice(0, 500)}`);
49 return response.json();
50}
51function localSQL(sql) {
52 const file = resolve(dir, "fixture.sql");
53 writeFileSync(file, sql);
54 const result = spawnSync(
55 process.execPath,
56 [
57 "node_modules/wrangler/bin/wrangler.js",
58 "d1",
59 "execute",
60 "billing-audit",
61 "--local",
62 "--config",
63 "target/billing-audit/wrangler.json",
64 "--persist-to",
65 "target/billing-audit/state",
66 "--file",
67 file,
68 "--json",
69 ],
70 { encoding: "utf8" },
71 );
72 if (result.status !== 0) throw Error(`Local fixture SQL failed: ${result.stderr.slice(0, 500)}`);
73}
74if (phase === "protocol") {
75 assert.equal((await rpc("status", {})).live, false);
76 const event = { id: `evt_audit_mode_${Date.now()}`, type: "audit.mode_check", livemode: true, data: { object: {} } };
77 const deliver = () => {
78 const payload = JSON.stringify(event),
79 time = Math.floor(Date.now() / 1000);
80 const signature = `t=${time},v1=${createHmac("sha256", secret).update(`${time}.${payload}`).digest("hex")}`;
81 return rpc("stripe_webhook", { payload, signature });
82 };
83 await assert.rejects(deliver(), /HTTP 500/);
84 event.livemode = false;
85 assert.deepEqual(await deliver(), { ok: true, value: true });
86 assert.deepEqual(await deliver(), { ok: true, value: false });
87 console.log("PASS wrong-mode delivery remains unclaimed and can be processed once after mode correction");
88} else if (phase === "seed") {
89 const workspace = `audit-${Date.now()}`;
90 const customer = await stripe("/customers", {
91 name: "Disposable g1t billing audit",
92 "metadata[g1t_audit]": workspace,
93 payment_method: "pm_card_visa",
94 "invoice_settings[default_payment_method]": "pm_card_visa",
95 "address[country]": "US",
96 "address[state]": "CO",
97 "address[postal_code]": "80202",
98 "address[line1]": "123 Test Street",
99 "address[city]": "Denver",
100 });
101 assert.match(customer.id, /^cus_[a-zA-Z0-9]+$/);
102 writeFileSync(fixturePath, JSON.stringify({ workspace, customer: customer.id, stripeAccount: auditAccount.id }));
103 localSQL(`INSERT INTO accounts (workspace,balance_micros,customer_id,created_at) VALUES ('${workspace}',0,'${customer.id}','${new Date().toISOString()}');`);
104 console.log(JSON.stringify({ seeded: workspace, mode: "sandbox", database: "local" }));
105} else if (phase === "run") {
106 const fixture = JSON.parse(readFileSync(fixturePath, "utf8"));
107 assert.equal(fixture.stripeAccount, auditAccount.id, "Fixture belongs to a different Stripe account; seed an isolated fixture");
108 const { workspace, customer } = fixture;
109 const actor = { id: "usr_audit", username: "audit-owner", verified: true, workspaces: [{ slug: workspace, role: "owner" }] };
110 const checks = [];
111 const passed = (name) => {
112 checks.push(name);
113 console.log(`PASS ${name}`);
114 };
115 assert.equal((await rpc("status", {})).live, false);
116 const started = await rpc("subscribe", { workspace, actor, feature: "plan", returnUrl: `${endpoint}/return` });
117 assert.equal(started.ok, true, JSON.stringify(started));
118 const features = await rpc("features", { workspace, viewer: actor });
119 assert.ok(features.ok && features.value.some((f) => f.plan.feature === "plan" && f.on), JSON.stringify(features));
120 passed("saved-card subscription activates through the real billing Worker");
121 const subscriptions = await stripe(`/subscriptions?customer=${customer}&status=all`);
122 assert.equal(subscriptions.data.length, 1);
123 fixture.subscription = subscriptions.data[0].id;
124 writeFileSync(fixturePath, JSON.stringify(fixture));
125 const again = await rpc("subscribe", { workspace, actor, feature: "plan", returnUrl: `${endpoint}/return` });
126 assert.equal(again.ok, false);
127 assert.equal((await stripe(`/subscriptions?customer=${customer}&status=all`)).data.length, 1);
128 passed("repeat subscribe cannot create a second active plan");
129 for (const resume of [false, true]) {
130 const changed = await rpc("cancel_subscription", { workspace, actor, feature: "plan", resume });
131 assert.equal(changed.ok, true, JSON.stringify(changed));
132 assert.equal((await stripe(`/subscriptions/${fixture.subscription}`)).cancel_at_period_end, !resume);
133 }
134 passed("period-end cancellation and resumption reach Stripe");
135 const charge = { workspace, feature: "deployments", costMicros: 15_000_000, description: "Sandbox audit usage", repo: null, reference: `audit/${workspace}` };
136 const posts = await Promise.all([rpc("charge_feature", charge), rpc("charge_feature", charge)]);
137 assert.equal(posts.filter((p) => p.ok && p.value).length, 1);
138 const account = await rpc("account", { workspace, viewer: actor });
139 assert.equal(account.value.balanceMicros, -8_000_000, JSON.stringify(account));
140 passed("concurrent usage delivery consumes included credit and posts excess once");
141 const closed = await rpc("close_workspace", { workspace, actor, dryRun: false });
142 assert.equal(closed.ok, true, JSON.stringify(closed));
143 const invoices = await rpc("invoices", { workspace, viewer: actor });
144 assert.ok(invoices.ok && invoices.value.some((i) => i.status === "paid"), JSON.stringify(invoices));
145 assert.equal((await rpc("account", { workspace, viewer: actor })).value.balanceMicros, 0);
146 const usageInvoice = (await stripe(`/invoices?customer=${customer}&limit=20`)).data.find((i) => i.metadata?.g1t_workspace === workspace);
147 assert.ok(usageInvoice?.charge);
148 passed("closing collects exactly the excess usage on a paid Stripe invoice");
149 await stripe("/refunds", { charge: usageInvoice.charge, amount: "400" });
150 const chargeObject = await stripe(`/charges/${usageInvoice.charge}`);
151 const event = { id: `evt_local_audit_${workspace}`, type: "charge.refunded", livemode: false, data: { object: chargeObject } };
152 const payload = JSON.stringify(event),
153 time = Math.floor(Date.now() / 1000);
154 const signature = `t=${time},v1=${createHmac("sha256", secret).update(`${time}.${payload}`).digest("hex")}`;
155 assert.equal((await rpc("stripe_webhook", { payload, signature })).ok, true);
156 assert.equal((await rpc("stripe_webhook", { payload, signature })).ok, true);
157 assert.equal((await rpc("account", { workspace, viewer: actor })).value.balanceMicros, -4_000_000);
158 passed("real sandbox refund and locally signed duplicate delivery reverse credit once");
159 const bad = await rpc("stripe_webhook", { payload, signature: "t=1,v1=00" });
160 assert.equal(bad.ok, false);
161 passed("invalid webhook signatures are rejected");
162 writeFileSync(
163 resolve(dir, "sandbox-results.json"),
164 JSON.stringify(
165 {
166 checkedAt: new Date().toISOString(),
167 mode: "sandbox",
168 worker: "local",
169 checks,
170 limitations: ["No public webhook delivery", "No browser Checkout or 3DS", "No renewal test clock", "No production deployment"],
171 },
172 null,
173 2,
174 ),
175 );
176} else if (phase === "lifecycle") {
177 assert.equal((await rpc("status", {})).live, false);
178 const workspace = `audit-cycle-${Date.now()}`;
179 const actor = { id: "usr_audit", username: "audit-owner", verified: true, workspaces: [{ slug: workspace, role: "owner" }] };
180 const checks = [];
181 const passed = (message) => {
182 checks.push(message);
183 console.log(`PASS ${message}`);
184 };
185 const clock = await stripe("/test_helpers/test_clocks", { frozen_time: String(Math.floor(Date.now() / 1000)), name: workspace });
186 const evidence = { workspace, clock: clock.id, stripeAccount: auditAccount.id, checks };
187 const save = () => writeFileSync(resolve(dir, "lifecycle-results.json"), JSON.stringify({ ...evidence, checkedAt: new Date().toISOString() }, null, 2));
188 save();
189 const customer = await stripe("/customers", {
190 name: workspace,
191 test_clock: clock.id,
192 payment_method: "pm_card_visa",
193 "invoice_settings[default_payment_method]": "pm_card_visa",
194 "metadata[workspace]": workspace,
195 "address[country]": "US",
196 "address[state]": "CO",
197 "address[postal_code]": "80202",
198 "address[line1]": "123 Test Street",
199 "address[city]": "Denver",
200 });
201 evidence.customer = customer.id;
202 save();
203 localSQL(`INSERT INTO accounts (workspace,balance_micros,customer_id,created_at) VALUES ('${workspace}',0,'${customer.id}','${new Date().toISOString()}');`);
204 const started = await rpc("subscribe", { workspace, actor, feature: "plan", returnUrl: `${endpoint}/return` });
205 assert.equal(started.ok, true, JSON.stringify(started));
206 const subscription = (await stripe(`/subscriptions?customer=${customer.id}&status=all`)).data[0];
207 assert.equal(subscription.status, "active");
208 evidence.subscription = subscription.id;
209 save();
210 const end = (sub) => sub.current_period_end ?? sub.items.data[0].current_period_end;
211 const advance = async (frozenTime) => {
212 await stripe(`/test_helpers/test_clocks/${clock.id}/advance`, { frozen_time: String(frozenTime) });
213 for (let attempt = 0; attempt < 30; attempt++) {
214 const current = await stripe(`/test_helpers/test_clocks/${clock.id}`);
215 if (current.status === "ready") return;
216 await new Promise((resolve) => setTimeout(resolve, 1000));
217 }
218 throw Error("Stripe clock did not become ready within 30 polls");
219 };
220 // Exercise the actual subscription handler and signed delivery protocol.
221 // These locally signed events deliberately fetch Stripe's current object;
222 // public webhook transport is a separate test, not claimed here.
223 const deliver = async (type, object, suffix) => {
224 // Read real objects in the production destination's schema while the
225 // Worker keeps using its independently pinned request API version.
226 const webhookVersion = "2026-08-26.dahlia";
227 const resource = type.startsWith("invoice.") ? "invoices" : "subscriptions";
228 object = await stripe(`/${resource}/${object.id}`, undefined, "GET", webhookVersion);
229 if (resource === "invoices") {
230 assert.equal(object.subscription, undefined);
231 assert.equal(object.parent?.subscription_details?.subscription, subscription.id);
232 }
233 const payload = JSON.stringify({ id: `evt_audit_${workspace}_${suffix}`, api_version: webhookVersion, type, livemode: false, data: { object } });
234 const time = Math.floor(Date.now() / 1000);
235 const signature = `t=${time},v1=${createHmac("sha256", secret).update(`${time}.${payload}`).digest("hex")}`;
236 const result = await rpc("stripe_webhook", { payload, signature });
237 assert.equal(result.ok, true, JSON.stringify(result));
238 };
239 const plan = async () => (await rpc("features", { workspace, viewer: actor })).value.find((f) => f.plan.feature === "plan");
240 try {
241 const waiver = await rpc("admin_set_allowances", {
242 id: `ws_${workspace}`,
243 allowances: { plan: true },
244 note: "Audit: must not silently waive an active Stripe plan",
245 by: "audit",
246 });
247 assert.equal(waiver.ok, false);
248 assert.match(waiver.error.message, /subscription/i);
249 assert.equal((await stripe(`/subscriptions/${subscription.id}`)).status, "active");
250 passed("staff fee waiver refuses a still-billable Stripe subscription");
251 await advance(end(subscription) + 7200);
252 let renewed = await stripe(`/subscriptions/${subscription.id}`);
253 let renewalInvoice = await stripe(`/invoices/${renewed.latest_invoice}`);
254 // Stripe can leave the renewal draft until the next simulation advance.
255 if (renewalInvoice.status === "draft") {
256 await stripe(`/invoices/${renewalInvoice.id}/finalize`, {});
257 renewalInvoice = await stripe(`/invoices/${renewalInvoice.id}`);
258 }
259 if (renewalInvoice.status === "open") renewalInvoice = await stripe(`/invoices/${renewalInvoice.id}/pay`, {});
260 assert.equal(renewalInvoice.status, "paid");
261 assert.notEqual(renewalInvoice.id, subscription.latest_invoice);
262 await deliver("invoice.paid", renewalInvoice, "renewed");
263 await deliver("invoice.paid", renewalInvoice, "renewed");
264 assert.equal((await plan()).on, true);
265 passed("test-clock renewal collects and duplicate delivery retains paid access");
266 save();
267 // Attach succeeds, subsequent charge fails: Stripe's documented test PM.
268 const declined = await stripe("/payment_methods/pm_card_chargeCustomerFail/attach", { customer: customer.id });
269 await stripe(`/subscriptions/${subscription.id}`, { default_payment_method: declined.id });
270 renewed = await stripe(`/subscriptions/${subscription.id}`);
271 await advance(end(renewed) + 7200);
272 let failedSub = await stripe(`/subscriptions/${subscription.id}`);
273 let failedInvoice = await stripe(`/invoices/${failedSub.latest_invoice}`);
274 if (failedInvoice.status === "draft") {
275 await stripe(`/invoices/${failedInvoice.id}/finalize`, {});
276 failedInvoice = await stripe(`/invoices/${failedInvoice.id}`);
277 }
278 if (!failedInvoice.attempted) {
279 await assert.rejects(stripe(`/invoices/${failedInvoice.id}/pay`, {}), /Stripe/);
280 }
281 failedSub = await stripe(`/subscriptions/${subscription.id}`);
282 assert.equal(failedSub.status, "past_due");
283 await deliver("invoice.payment_failed", failedInvoice, "declined");
284 assert.equal((await plan()).on, false);
285 passed("declined renewal removes paid access after reconciliation");
286 save();
287 await stripe(`/subscriptions/${subscription.id}`, { default_payment_method: customer.invoice_settings.default_payment_method });
288 const recovered = await stripe(`/invoices/${failedInvoice.id}/pay`, {});
289 assert.equal(recovered.status, "paid");
290 await deliver("invoice.paid", recovered, "recovered");
291 await deliver("invoice.payment_failed", failedInvoice, "late-decline");
292 assert.equal((await plan()).on, true);
293 passed("payment recovery restores access and stale decline cannot undo it");
294 save();
295 const cancel = await rpc("cancel_subscription", { workspace, actor, feature: "plan", resume: false });
296 assert.equal(cancel.ok, true, JSON.stringify(cancel));
297 assert.equal((await plan()).on, true);
298 const beforeEnd = await stripe(`/subscriptions/${subscription.id}`);
299 await advance(end(beforeEnd) + 60);
300 const ended = await stripe(`/subscriptions/${subscription.id}`);
301 assert.equal(ended.status, "canceled");
302 await deliver("customer.subscription.deleted", ended, "ended");
303 assert.equal((await plan()).on, false);
304 passed("period-end cancellation retains access until expiry and then removes it");
305 evidence.webhookObjectVersion = "2026-08-26.dahlia";
306 evidence.transport = "Locally signed RPC delivery; not public Stripe delivery";
307 evidence.complete = true;
308 save();
309 } finally {
310 await stripe(`/test_helpers/test_clocks/${clock.id}`, undefined, "DELETE");
311 evidence.cleaned = true;
312 save();
313 }
314} else if (phase === "enterprise") {
315 const workspace = `audit-enterprise-${Date.now()}`;
316 const enterprise = `ent_${workspace}`;
317 const customer = await stripe("/customers", {
318 name: workspace,
319 email: "billing-audit@example.com",
320 "address[country]": "US",
321 "address[state]": "CO",
322 "address[postal_code]": "80202",
323 "address[line1]": "123 Test Street",
324 "address[city]": "Denver",
325 });
326 const now = new Date().toISOString();
327 localSQL(`INSERT INTO billing_accounts (id,kind,name,customer_id,created_by,created_at) VALUES ('${enterprise}','enterprise','Audit enterprise','${customer.id}','audit','${now}');
328 INSERT INTO account_members VALUES ('${workspace}','${enterprise}','audit','${now}');
329 INSERT INTO accounts (workspace,balance_micros,created_at) VALUES ('${workspace}',-10000001,'${now}');`);
330 const checks = [];
331 try {
332 // Wait for both requests before cleanup, even when Stripe returns its
333 // transient idempotency_key_in_use response to one concurrent caller.
334 const outcomes = await Promise.allSettled([
335 rpc("admin_invoice_enterprise", { id: enterprise, by: "audit" }),
336 rpc("admin_invoice_enterprise", { id: enterprise, by: "audit" }),
337 ]);
338 const requests = outcomes.filter((r) => r.status === "fulfilled").map((r) => r.value);
339 for (const result of outcomes) assert.equal(result.status, "fulfilled", result.reason?.message);
340 assert.ok(
341 requests.some((r) => r.ok),
342 JSON.stringify(requests),
343 );
344 const invoices = (await stripe(`/invoices?customer=${customer.id}`)).data;
345 assert.equal(invoices.length, 1);
346 assert.equal(invoices[0].subtotal, 1001);
347 for (const result of requests.filter((r) => r.ok)) assert.equal(result.value.lines[0].amountMicros, 10010000);
348 checks.push("concurrent enterprise requests create one invoice with exact cent-rounded line accounting");
349 console.log(`PASS ${checks.at(-1)}`);
350 const again = await rpc("admin_invoice_enterprise", { id: enterprise, by: "audit" });
351 assert.equal(again.ok, false);
352 assert.equal((await stripe(`/invoices?customer=${customer.id}`)).data.length, 1);
353 checks.push("already-invoiced debt cannot be invoiced twice");
354 console.log(`PASS ${checks.at(-1)}`);
355 localSQL(`UPDATE accounts SET balance_micros=balance_micros-5000000 WHERE workspace='${workspace}';`);
356 const more = await rpc("admin_invoice_enterprise", { id: enterprise, by: "audit" });
357 assert.equal(more.ok, true, JSON.stringify(more));
358 const all = (await stripe(`/invoices?customer=${customer.id}`)).data;
359 assert.equal(all.length, 2);
360 assert.equal(
361 all.reduce((sum, i) => sum + i.subtotal, 0),
362 1501,
363 );
364 checks.push("subsequent usage invoices only debt not covered by the first invoice");
365 console.log(`PASS ${checks.at(-1)}`);
366 writeFileSync(
367 resolve(dir, "enterprise-results.json"),
368 JSON.stringify({ checkedAt: new Date().toISOString(), stripeAccount: auditAccount.id, workspace, checks }, null, 2),
369 );
370 } finally {
371 await stripe(`/customers/${customer.id}`, undefined, "DELETE");
372 }
373} else if (phase === "complimentary") {
374 // Staff enrollment, terms changes, admission, conversion (paid, 3DS,
375 // declined, no card, repeated) and revocation, through the real Worker
376 // against real sandbox subscriptions. Webhooks are not delivered: each
377 // step's synchronous path is what is exercised.
378 assert.equal((await rpc("status", {})).live, false);
379 const stamp = Date.now();
380 const checks = [];
381 const made = [];
382 const passed = (message) => {
383 checks.push(message);
384 console.log(`PASS ${message}`);
385 };
386 const actorOf = (workspace) => ({ id: "usr_audit", username: "audit-owner", verified: true, workspaces: [{ slug: workspace, role: "owner" }] });
387 const enroll = (workspace, allowance, fee = 0) =>
388 rpc("admin_set_complimentary", { workspace, allowance_micros: allowance, unlimited: false, fee_cents: fee, note: "sandbox audit", by: "audit" });
389 const standing = (workspace) => rpc("admin_complimentary", { workspace });
390 const reserve = (workspace, estimate) =>
391 rpc("reserve", { workspace, repo: { namespace: workspace, name: "app" }, public: false, kind: "workflow", estimateMicros: estimate });
392 const card = async (customer, method) => {
393 const pm = await stripe(`/payment_methods/${method}/attach`, { customer });
394 await stripe(`/customers/${customer}`, { "invoice_settings[default_payment_method]": pm.id });
395 };
396 const convert = (workspace) => rpc("convert_to_paid", { actor: actorOf(workspace), workspace, returnUrl: `${endpoint}/return` });
397 try {
398 // Enrollment: a real customer and subscription, then visible.
399 const a = `cmp-a-${stamp}`;
400 const enrolled = await enroll(a, 100_000_000);
401 assert.equal(enrolled.ok, true, JSON.stringify(enrolled));
402 made.push(enrolled.value.customerId);
403 assert.equal(enrolled.value.status, "active", JSON.stringify(enrolled.value));
404 const sub = await stripe(`/subscriptions/${enrolled.value.subscriptionId}`);
405 assert.equal(sub.status, "active");
406 assert.equal(sub.metadata.g1t_grant, enrolled.value.grantId);
407 assert.equal(sub.metadata.g1t_allowance_micros, "100000000");
408 assert.equal(sub.items.data[0].price.unit_amount, 0);
409 const ent = await rpc("entitlements", { workspace: a });
410 assert.equal(ent.plan, "complimentary");
411 assert.equal(ent.complimentary.allowanceMicros, 100_000_000);
412 assert.equal(ent.complimentary.remainingMicros, 100_000_000);
413 assert.equal(ent.hasPlan, true);
414 passed("staff enrollment creates a real $0 subscription with the grant in its metadata, then the workspace sees it");
415 const again = await enroll(a, 100_000_000);
416 assert.equal(again.value.subscriptionId, enrolled.value.subscriptionId);
417 assert.equal((await stripe(`/subscriptions?customer=${enrolled.value.customerId}&status=all`)).data.length, 1);
418 passed("changing terms keeps one subscription");
419 // Admission: the allowance bounds new work, card or not; no on demand.
420 await card(enrolled.value.customerId, "pm_card_visa");
421 const big = await reserve(a, 200_000_000);
422 assert.equal(big.ok, false, JSON.stringify(big));
423 assert.match(big.error.message, /complimentary allowance/);
424 const small = await reserve(a, 10_000_000);
425 assert.equal(small.ok, true, JSON.stringify(small));
426 const held = await rpc("entitlements", { workspace: a });
427 assert.ok(held.complimentary.remainingMicros < 100_000_000 && held.complimentary.heldMicros > 0, JSON.stringify(held.complimentary));
428 passed("admission counts running work against the allowance and refuses past it even with a saved card");
429 // A custom fee is its own price on the same subscription.
430 const fee = await enroll(a, 50_000_000, 2_500);
431 assert.equal(fee.ok, true, JSON.stringify(fee));
432 const feeSub = await stripe(`/subscriptions/${enrolled.value.subscriptionId}`);
433 assert.equal(feeSub.items.data[0].price.unit_amount, 2_500);
434 assert.equal(feeSub.metadata.g1t_fee_cents, "2500");
435 assert.equal(feeSub.collection_method, "send_invoice");
436 assert.equal((await standing(a)).periods.length, 3);
437 passed("a custom monthly fee changes the subscription price apart from the allowance, each term kept as a period");
438 assert.equal((await enroll(a, 50_000_000, 0)).ok, true);
439 // Conversion with a card that pays: the same subscription becomes the plan.
440 const converted = await convert(a);
441 assert.equal(converted.ok, true, JSON.stringify(converted));
442 assert.equal(converted.value.status, "converted", JSON.stringify(converted.value));
443 const paid = await stripe(`/subscriptions/${enrolled.value.subscriptionId}`);
444 assert.equal(paid.items.data[0].price.unit_amount, 2_100);
445 assert.equal((await stripe(`/subscriptions?customer=${enrolled.value.customerId}&status=all`)).data.length, 1);
446 assert.equal((await rpc("entitlements", { workspace: a })).plan, "paid");
447 const ended = await standing(a);
448 assert.equal(ended.status, "ended");
449 assert.equal(ended.endedReason, "converted");
450 assert.ok(ended.moneyReceivedMicros >= 21_000_000, JSON.stringify(ended));
451 passed("conversion with a paying card turns the same subscription into the plan and records its payment as money received");
452
453 // 3DS: nothing changes until the bank is answered; asking again is the same invoice.
454 const b = `cmp-b-${stamp}`;
455 const bEnrolled = await enroll(b, 100_000_000);
456 made.push(bEnrolled.value.customerId);
457 await card(bEnrolled.value.customerId, "pm_card_authenticationRequired");
458 const needs = await convert(b);
459 assert.equal(needs.value?.status, "needs_action", JSON.stringify(needs));
460 assert.match(needs.value.url, /^https:\/\/invoice\.stripe\.com\//);
461 const invoicesBefore = (await stripe(`/invoices?subscription=${bEnrolled.value.subscriptionId}`)).data.length;
462 const twice = await convert(b);
463 assert.equal(twice.value?.status, "needs_action", JSON.stringify(twice));
464 // Hosted invoice links are re-signed on each read: the invoice count below is the check.
465 assert.equal((await stripe(`/invoices?subscription=${bEnrolled.value.subscriptionId}`)).data.length, invoicesBefore);
466 assert.equal((await rpc("entitlements", { workspace: b })).plan, "complimentary");
467 assert.equal((await stripe(`/subscriptions/${bEnrolled.value.subscriptionId}`)).items.data[0].price.unit_amount, 0);
468 assert.equal((await standing(b)).status, "active");
469 passed("3DS leaves the enrollment as it was, and asking again returns the same invoice rather than a new one");
470
471 // Declined: the enrollment stands, one subscription.
472 const c = `cmp-c-${stamp}`;
473 const cEnrolled = await enroll(c, 100_000_000);
474 made.push(cEnrolled.value.customerId);
475 await card(cEnrolled.value.customerId, "pm_card_chargeCustomerFail");
476 const declined = await convert(c);
477 assert.equal(declined.value?.status, "declined", JSON.stringify(declined));
478 assert.equal((await rpc("entitlements", { workspace: c })).plan, "complimentary");
479 assert.equal((await stripe(`/subscriptions?customer=${cEnrolled.value.customerId}&status=all`)).data.length, 1);
480 passed("a declined card leaves the enrollment and its one subscription unchanged");
481
482 // No card at all: a card check page, nothing charged.
483 const d = `cmp-d-${stamp}`;
484 const dEnrolled = await enroll(d, 100_000_000);
485 made.push(dEnrolled.value.customerId);
486 const needsCard = await convert(d);
487 assert.equal(needsCard.value?.status, "needs_card", JSON.stringify(needsCard));
488 assert.match(needsCard.value.url, /^https:\/\/checkout\.stripe\.com\//);
489 passed("without a saved card, conversion sends the owner to a card check first");
490 const member = { id: "usr_member", username: "member", verified: true, workspaces: [{ slug: d, role: "member" }] };
491 assert.equal((await rpc("convert_to_paid", { actor: member, workspace: d, returnUrl: `${endpoint}/return` })).ok, false);
492 passed("a member who does not manage billing cannot convert");
493
494 // Charged past the allowance (employees, friends): refused without a
495 // card, then admitted under the workspace's limits once it has one.
496 const e = `cmp-e-${stamp}`;
497 const eEnrolled = await rpc("admin_set_complimentary", {
498 workspace: e,
499 allowance_micros: 5_000_000,
500 unlimited: false,
501 fee_cents: 0,
502 charge_past_allowance: true,
503 note: "sandbox audit",
504 by: "audit",
505 });
506 assert.equal(eEnrolled.ok, true, JSON.stringify(eEnrolled));
507 made.push(eEnrolled.value.customerId);
508 assert.equal(eEnrolled.value.chargePastAllowance, true);
509 assert.equal((await stripe(`/subscriptions/${eEnrolled.value.subscriptionId}`)).metadata.g1t_past_allowance, "charge");
510 const noCard = await reserve(e, 20_000_000);
511 assert.equal(noCard.ok, false, JSON.stringify(noCard));
512 assert.match(noCard.error.message, /Add a card/);
513 const eEnt = await rpc("entitlements", { workspace: e });
514 assert.equal(eEnt.complimentary.chargePastAllowance, true);
515 assert.equal(eEnt.complimentary.hasCard, false);
516 passed("charged past the allowance, without a card: work past it is refused and the owner is told to add one");
517 await card(eEnrolled.value.customerId, "pm_card_visa");
518 localSQL(`UPDATE accounts SET card_last4 = '4242' WHERE workspace = '${e}';`);
519 const withCard = await reserve(e, 20_000_000);
520 assert.equal(withCard.ok, true, JSON.stringify(withCard));
521 const eAfter = await rpc("entitlements", { workspace: e });
522 assert.equal(eAfter.hasPlan, true);
523 assert.ok(eAfter.ceilingMicros > 0, JSON.stringify(eAfter));
524 passed("charged past the allowance, with a card: work past it is admitted under the workspace's own limits");
525
526 // Revocation: back to Free now, subscription canceled.
527 const revoked = await rpc("admin_end_complimentary", { workspace: d, note: "sandbox audit", by: "audit" });
528 assert.equal(revoked.ok, true, JSON.stringify(revoked));
529 assert.equal(revoked.value.status, "ended");
530 assert.equal((await stripe(`/subscriptions/${dEnrolled.value.subscriptionId}`)).status, "canceled");
531 assert.equal((await rpc("entitlements", { workspace: d })).plan, "free");
532 passed("ending an enrollment cancels its subscription and returns the workspace to Free");
533 writeFileSync(
534 resolve(dir, "complimentary-results.json"),
535 JSON.stringify(
536 {
537 checkedAt: new Date().toISOString(),
538 stripeAccount: auditAccount.id,
539 mode: "sandbox",
540 worker: "local",
541 checks,
542 limitations: ["Webhooks not delivered publicly", "3DS not completed in a browser here", "No test clock renewal of an enrollment"],
543 },
544 null,
545 2,
546 ),
547 );
548 } finally {
549 for (const customer of made.filter(Boolean)) {
550 try {
551 await stripe(`/customers/${customer}`, undefined, "DELETE");
552 } catch (error) {
553 console.error(`cleanup ${customer}: ${error.message}`);
554 }
555 }
556 }
557} else if (phase === "cleanup") {
558 const { customer } = JSON.parse(readFileSync(fixturePath, "utf8"));
559 await stripe(`/customers/${customer}`, undefined, "DELETE");
560 console.log("Disposable sandbox customer removed; live account untouched.");
561} else throw Error("Use seed, run or cleanup");