tests/e2e/billing-settings.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import {
buildE2EAuthHeaders,
enableScopedE2EAuth,
scopedPath,
} from "./helpers/auth";
import { capacitySummary } from "./helpers/billing";
import {
fulfillJson,
mockSettingsShell,
} from "./helpers/billing-settings-fixtures";
const TEAM_ID = "00000000-0000-4000-8000-000000000002";
const TEAM_SLUG = "acme";
test("personal Billing shows capacity and reviews an add-on change", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(route, {
...capacitySummary({
concurrencyPurchasesEnabled: true,
hostedUsage: {
includedRemainingCents: 0,
purchasedRemainingCents: 1_000,
openReservationsCents: 1_000,
spendableCents: 0,
grantResetsAt: "2026-09-17T12:00:00.000Z",
purchasesFrozen: false,
},
}),
billingDetails: {
hasMoreInvoices: true,
paymentMethod: {
brand: "visa",
last4: "4242",
expMonth: 12,
expYear: 2028,
},
invoices: [
{
id: "in_credit",
number: "MPX-002",
description: "$25 inference credit",
status: "paid",
amountCents: 2_500,
currency: "usd",
createdAt: "2026-08-17T12:00:00.000Z",
hostedInvoiceUrl: "https://invoice.stripe.test/in_credit",
invoicePdfUrl: "https://invoice.stripe.test/in_credit.pdf",
},
{
id: "in_plan",
number: "MPX-001",
description: "Mogplex Plus",
status: "paid",
amountCents: 10_000,
currency: "usd",
createdAt: "2026-07-17T12:00:00.000Z",
hostedInvoiceUrl: "https://invoice.stripe.test/in_plan",
invoicePdfUrl: "https://invoice.stripe.test/in_plan.pdf",
},
],
},
})
);
await page.route("**/api/billing/capacity/preview", async (route) => {
expect(route.request().postDataJSON()).toEqual({
lookupKey: "capacity_v2_concurrency_10_monthly",
quantity: 2,
effectiveAction: "increase",
});
await fulfillJson(route, {
resource: "concurrency",
lookupKey: "capacity_v2_concurrency_10_monthly",
name: "Parallel agent runs +10",
action: "increase",
currentQuantity: 1,
resultingQuantity: 2,
currentAllowance: "35",
resultingAllowance: "45",
currentRecurringAmountCents: 500,
resultingRecurringAmountCents: 1_000,
recurringChangeCents: 500,
amountDueNowCents: 250,
currency: "usd",
taxStatus: "calculated",
effectiveAt: "2026-08-17T12:00:00.000Z",
effectiveTiming: "after_payment",
previewToken: "preview-token",
expiresAt: "2026-08-17T12:10:00.000Z",
});
});
await page.route("**/api/billing/capacity/checkout", (route) =>
fulfillJson(route, {
status: "submitted",
paymentUrl: null,
entitlementStatus: "pending_webhook",
})
);
await page.route("**/api/billing/hosted-usage/checkout", (route) =>
fulfillJson(route, {
status: "checkout_created",
url: scopedPath("settings?tab=billing&billing=hosted-usage-submitted"),
creditCents: 1_000,
subtotalCents: 1_000,
currency: "usd",
balanceStatus: "pending_webhook",
})
);
await page.route("**/api/stripe/portal", (route) =>
fulfillJson(route, { url: scopedPath("settings?tab=billing") })
);
await page.goto(scopedPath("settings?tab=billing"));
await expect(page.getByRole("heading", { name: "Plus" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Add inference credit" })
).toBeVisible();
const creditChoices = page.getByRole("group", {
name: "Choose credit amount",
});
await expect(creditChoices.getByRole("button")).toHaveCount(7);
await expect(
creditChoices.getByRole("button", {
name: "Pay $1, get $1 credit",
})
).toBeVisible();
await expect(page.getByText("Monthly plan cost")).toBeVisible();
await expect(page.getByText("$100.00 / month")).toBeVisible();
await expect(page.getByText("35 parallel agent runs")).toBeVisible();
await expect(page.getByText("25 with plan, 10 add-on")).toBeVisible();
await expect(page.getByText("Next due date")).toBeVisible();
await expect(page.getByText("Sep 17, 2026")).toBeVisible();
await expect(page.getByText("Visa ending in 4242")).toBeVisible();
await expect(page.getByText("Expires 12/2028")).toBeVisible();
await expect(
page.getByRole("button", { name: "Update payment method" })
).toBeVisible();
await expect(page.getByText("$25 inference credit")).toBeVisible();
await expect(page.getByText("Mogplex Plus")).toBeVisible();
await expect(
page.getByRole("link", { name: "Invoice MPX-002" })
).toHaveAttribute("href", "https://invoice.stripe.test/in_credit");
await expect(
page.getByRole("button", { name: "View all invoices" })
).toBeVisible();
await expect(
page.getByRole("link", { name: "support@mogplex.com" })
).toHaveAttribute("href", "mailto:support@mogplex.com");
await expect(
page.getByRole("heading", { name: "Parallel agent runs" })
).toBeVisible();
await expect(page.getByRole("heading", { name: "Storage" })).toBeVisible();
await expect(page.getByText("Hosted usage", { exact: true })).toHaveCount(0);
await expect(page.getByText("Retained data", { exact: true })).toHaveCount(0);
await expect(page.getByText("Automatic burst", { exact: true })).toHaveCount(
0
);
await expect(page.getByText("Not enforced", { exact: true })).toHaveCount(0);
const headings = await page.locator("h2").allTextContents();
expect(headings.slice(0, 2)).toEqual(["Plus", "Add inference credit"]);
await expect(page.getByText("7 of 35")).toHaveCount(0);
await expect(page.getByText("2.3 GB of 5 GB")).toHaveCount(0);
await page.getByRole("tab", { name: "Usage", exact: true }).click();
await expect(page.getByText(/Code review .*#285/)).toBeVisible();
await page
.getByRole("tab", { name: "Billing Settings", exact: true })
.click();
const inferenceCheckout = page.waitForRequest(
"**/api/billing/hosted-usage/checkout"
);
await page.getByRole("button", { name: "Pay $10, get $10 credit" }).click();
expect((await inferenceCheckout).postDataJSON()).toMatchObject({
preset: "capacity_v2_hosted_usage_credit_10",
returnPath: scopedPath("settings/billing"),
});
await expect(
page.getByText("Payment submitted. Stripe will add the full credit amount.")
).toBeVisible();
await page
.getByRole("button", { name: "Manage Parallel agent runs +10" })
.click();
await page.getByRole("button", { name: "Increase quantity" }).click();
await page.getByRole("button", { name: "Review change" }).click();
await expect(page.getByText("35 to 45")).toBeVisible();
await expect(page.getByText("$2.50")).toBeVisible();
const confirmRequest = page.waitForRequest(
"**/api/billing/capacity/checkout"
);
await page.getByRole("button", { name: "Confirm change" }).click();
expect((await confirmRequest).postDataJSON()).toMatchObject({
previewToken: "preview-token",
});
await expect(page.getByRole("dialog")).toHaveCount(0);
const portalRequest = page.waitForRequest("**/api/stripe/portal");
await page.getByRole("button", { name: "Update payment method" }).click();
await portalRequest;
});
test("ordinary Billing hides plan-included capacity meters without an upsell", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(
route,
capacitySummary({
concurrencyPurchasesEnabled: false,
concurrency: {
active: 7,
included: 25,
addOn: 0,
limit: 25,
wouldBlock: false,
},
addOns: [],
})
)
);
await page.goto(scopedPath("settings?tab=billing"));
await expect(page.getByText("Automatic burst", { exact: true })).toHaveCount(
0
);
await expect(page.getByText("7 of 25")).toHaveCount(0);
await expect(page.getByText("25 parallel agent runs")).toBeVisible();
await expect(
page.getByRole("heading", { name: "Storage add-ons" })
).toBeVisible();
await expect(
page.getByRole("button", { name: /Parallel agent runs \+/ })
).toHaveCount(0);
await expect(
page.getByRole("button", { name: "Add Storage +1 GB" })
).toBeVisible();
});
test("existing concurrency customers can reduce but not increase outside the pilot", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(route, capacitySummary({ concurrencyPurchasesEnabled: false }))
);
await page.goto(scopedPath("settings?tab=billing"));
await page
.getByRole("button", { name: "Manage Parallel agent runs +10" })
.click();
await expect(
page.getByText(
"Extra parallel agent runs are not available for this account. You can keep or reduce your current quantity."
)
).toBeVisible();
await expect(
page.getByRole("button", { name: "Increase quantity" })
).toBeDisabled();
await expect(
page.getByRole("button", { name: "Decrease quantity" })
).toBeEnabled();
});
test("company Billing omits capacity add-ons for a member", async ({
page,
}) => {
await page.context().setExtraHTTPHeaders({
...buildE2EAuthHeaders("00000000-0000-4000-8000-000000000001"),
"x-mogplex-scope-kind": "team",
"x-mogplex-scope-slug": TEAM_SLUG,
"x-mogplex-scope-id": TEAM_ID,
});
await mockSettingsShell(page);
await page.route(`**/api/teams/${TEAM_ID}/members`, (route) =>
fulfillJson(route, {
team: { id: TEAM_ID, slug: TEAM_SLUG, name: "Acme", icon_url: null },
members: [],
invites: [],
viewer: { userId: "member-1", role: "developer", canManage: false },
})
);
await page.route(`**/api/teams/${TEAM_ID}/keys`, (route) =>
fulfillJson(route, {
keys: [],
viewer: { role: "developer", canManage: false },
})
);
await page.route(`**/api/teams/${TEAM_ID}/models`, (route) =>
fulfillJson(route, {
modelAllowlist: null,
viewer: { role: "developer", canManage: false },
})
);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(
route,
capacitySummary({
account: {
id: "billing-account-2",
eventSequence: "4",
enforcementMode: "shadow",
scope: "team",
displayName: "Acme",
status: "read_only",
canManageBilling: false,
hasSubscription: true,
hasBillingHistory: true,
},
plan: {
ref: "business",
name: "Business",
offerKind: "contract",
interval: "contract",
recurringAmountCents: null,
renewsAt: null,
cancelsAt: null,
namedUserLimit: null,
},
})
)
);
await page.goto(`/${TEAM_SLUG}/settings?tab=billing`);
await expect(page.getByRole("heading", { name: "Business" })).toBeVisible();
await expect(
page.getByRole("heading", { name: /Capacity add-ons|Storage add-ons/ })
).toHaveCount(0);
await expect(page.getByRole("button", { name: /Add|Manage/ })).toHaveCount(0);
});
test("Billing explains when purchase operations are unavailable", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(
route,
capacitySummary({
billingOperationsEnabled: false,
})
)
);
await page.goto(scopedPath("settings?tab=billing"));
await expect(
page.getByText(
"Inference purchases are unavailable on this deployment. Ask the deployment administrator to configure billing."
)
).toBeVisible();
await expect(
page.getByText(
"Capacity purchases are unavailable on this deployment. Ask the deployment administrator to configure billing."
)
).toBeVisible();
await expect(
page.getByRole("button", { name: "$10", exact: true })
).toHaveCount(0);
await expect(
page.getByRole("button", { name: "Add Storage +1 GB" })
).toHaveCount(0);
});
test("legacy subscribers can manage their plan and buy inference credit", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(
route,
capacitySummary({
billingOperationsEnabled: false,
concurrency: {
active: 0,
included: 0,
addOn: 0,
limit: 0,
wouldBlock: false,
},
plan: {
ref: "legacy",
name: "Mog Mode",
offerKind: "legacy",
interval: "contract",
recurringAmountCents: null,
renewsAt: "2026-09-17T12:00:00.000Z",
cancelsAt: null,
namedUserLimit: null,
},
})
)
);
await page.route("**/api/stripe/checkout", (route) =>
fulfillJson(route, {
url: scopedPath("settings?tab=billing&billing=topup"),
})
);
await page.goto(scopedPath("settings?tab=billing"));
await expect(page.getByRole("heading", { name: "Mog Mode" })).toBeVisible();
await expect(page.getByRole("button", { name: "Manage plan" })).toBeEnabled();
await expect(page.getByText("0 parallel agent runs")).toHaveCount(0);
const topupCheckout = page.waitForRequest("**/api/stripe/checkout");
await page.getByRole("button", { name: "Pay $10, get $10 credit" }).click();
expect((await topupCheckout).postDataJSON()).toMatchObject({
kind: "topup",
preset: "topup_10",
});
});
test("top-up customers can open billing history without a subscription", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockSettingsShell(page);
await page.route("**/api/billing/capacity", (route) =>
fulfillJson(
route,
capacitySummary({
account: {
...capacitySummary().account,
hasSubscription: false,
hasBillingHistory: true,
},
})
)
);
await page.route("**/api/stripe/portal", (route) =>
fulfillJson(route, { url: scopedPath("settings?tab=billing") })
);
await page.goto(scopedPath("settings?tab=billing"));
const portalRequest = page.waitForRequest("**/api/stripe/portal");
await page.getByRole("button", { name: "Manage billing" }).click();
await portalRequest;
});