Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
148
tests/e2e/server_pricing.test.ts
Normal file
148
tests/e2e/server_pricing.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Server Pricing Suite
|
||||
*
|
||||
* The server — never the client — decides what a plan costs. These tests pin
|
||||
* the pure checkout builder against the shared price catalog (src/lib/billing/
|
||||
* pricing.ts): bogus plans fall back to annual, the catalog amounts (499 /
|
||||
* 3999 / 5999) are used verbatim when no Stripe Price ID is configured, and an
|
||||
* env Price ID wins when present. No real Stripe calls are made.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from "./runner";
|
||||
import { buildCheckoutParams } from "../../src/lib/billing/checkout";
|
||||
import { PLAN_CONFIGS } from "../../src/lib/billing/pricing";
|
||||
|
||||
const user = { id: "usr_test_checkout_1" };
|
||||
|
||||
describe("ServerPricing — plan resolution", () => {
|
||||
test("unknown or missing plans fall back to annual", () => {
|
||||
expect(buildCheckoutParams(user, "enterprise-ultra", {}).metadata.plan).toBe("annual");
|
||||
expect(buildCheckoutParams(user, undefined, {}).metadata.plan).toBe("annual");
|
||||
expect(buildCheckoutParams(user, null, {}).metadata.plan).toBe("annual");
|
||||
expect(buildCheckoutParams(user, "", {}).metadata.plan).toBe("annual");
|
||||
});
|
||||
|
||||
test("a bogus plan is charged the annual price, never an invented one", () => {
|
||||
const params = buildCheckoutParams(user, "FREE_FOREVER", {});
|
||||
expect(params.metadata.plan).toBe("annual");
|
||||
expect(params.lineItems[0].price_data?.unit_amount).toBe(PLAN_CONFIGS.annual.unitAmountMinor);
|
||||
expect(params.lineItems[0].price_data?.unit_amount).toBe(3999);
|
||||
});
|
||||
|
||||
test("known plan ids resolve to themselves", () => {
|
||||
expect(buildCheckoutParams(user, "weekly", {}).metadata.plan).toBe("weekly");
|
||||
expect(buildCheckoutParams(user, "annual", {}).metadata.plan).toBe("annual");
|
||||
expect(buildCheckoutParams(user, "lifetime", {}).metadata.plan).toBe("lifetime");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ServerPricing — catalog amounts", () => {
|
||||
test("without an env Price ID, unit_amount is exactly the catalog value per plan", () => {
|
||||
expect(buildCheckoutParams(user, "weekly", {}).lineItems[0].price_data?.unit_amount).toBe(499);
|
||||
expect(buildCheckoutParams(user, "annual", {}).lineItems[0].price_data?.unit_amount).toBe(3999);
|
||||
expect(buildCheckoutParams(user, "lifetime", {}).lineItems[0].price_data?.unit_amount).toBe(5999);
|
||||
});
|
||||
|
||||
test("the amounts stay pinned to PLAN_CONFIGS and never drift apart", () => {
|
||||
expect(buildCheckoutParams(user, "weekly", {}).lineItems[0].price_data?.unit_amount).toBe(
|
||||
PLAN_CONFIGS.weekly.unitAmountMinor
|
||||
);
|
||||
expect(buildCheckoutParams(user, "annual", {}).lineItems[0].price_data?.unit_amount).toBe(
|
||||
PLAN_CONFIGS.annual.unitAmountMinor
|
||||
);
|
||||
expect(buildCheckoutParams(user, "lifetime", {}).lineItems[0].price_data?.unit_amount).toBe(
|
||||
PLAN_CONFIGS.lifetime.unitAmountMinor
|
||||
);
|
||||
});
|
||||
|
||||
test("a client-supplied amount cannot influence the charged price", () => {
|
||||
// The builder accepts no amount parameter; the body can only carry a plan
|
||||
// id, so the catalog amount is what gets charged no matter what else a
|
||||
// tampered request tries to inject.
|
||||
const params = buildCheckoutParams(user, "annual", {} as any);
|
||||
expect(params.lineItems[0].price_data?.unit_amount).toBe(PLAN_CONFIGS.annual.unitAmountMinor);
|
||||
expect(params.lineItems[0].price_data?.unit_amount).toBe(3999);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ServerPricing — env Price ID precedence", () => {
|
||||
test("a configured env Price ID wins and suppresses price_data entirely", () => {
|
||||
const weekly = buildCheckoutParams(user, "weekly", {
|
||||
STRIPE_WEEKLY_PRICE_ID: "price_weekly_test",
|
||||
});
|
||||
expect(weekly.lineItems[0].price).toBe("price_weekly_test");
|
||||
expect(weekly.lineItems[0].price_data).toBeUndefined();
|
||||
|
||||
const annual = buildCheckoutParams(user, "annual", {
|
||||
STRIPE_ANNUAL_PRICE_ID: "price_annual_test",
|
||||
});
|
||||
expect(annual.lineItems[0].price).toBe("price_annual_test");
|
||||
expect(annual.lineItems[0].price_data).toBeUndefined();
|
||||
|
||||
const lifetime = buildCheckoutParams(user, "lifetime", {
|
||||
STRIPE_LIFETIME_PRICE_ID: "price_lifetime_test",
|
||||
});
|
||||
expect(lifetime.lineItems[0].price).toBe("price_lifetime_test");
|
||||
expect(lifetime.lineItems[0].price_data).toBeUndefined();
|
||||
});
|
||||
|
||||
test("the env Price ID comes from the plan's own catalog entry", () => {
|
||||
const weekly = buildCheckoutParams(user, "weekly", {
|
||||
[PLAN_CONFIGS.weekly.priceIdEnv]: "price_weekly_test",
|
||||
});
|
||||
expect(weekly.lineItems[0].price).toBe("price_weekly_test");
|
||||
expect(weekly.lineItems[0].price_data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ServerPricing — subscription shape", () => {
|
||||
test("weekly is a subscription with a 3-day trial and weekly billing", () => {
|
||||
const params = buildCheckoutParams(user, "weekly", {});
|
||||
expect(params.mode).toBe("subscription");
|
||||
expect(params.subscriptionData?.trial_period_days).toBe(3);
|
||||
expect(params.subscriptionData?.metadata?.plan).toBe("weekly");
|
||||
expect(params.lineItems[0].price_data?.recurring?.interval).toBe("week");
|
||||
});
|
||||
|
||||
test("annual is a subscription billed yearly, without any trial", () => {
|
||||
const params = buildCheckoutParams(user, "annual", {});
|
||||
expect(params.mode).toBe("subscription");
|
||||
expect(params.subscriptionData?.trial_period_days).toBeUndefined();
|
||||
expect(params.subscriptionData?.metadata?.plan).toBe("annual");
|
||||
expect(params.lineItems[0].price_data?.recurring?.interval).toBe("year");
|
||||
});
|
||||
|
||||
test("lifetime is a one-off payment without subscription data", () => {
|
||||
const params = buildCheckoutParams(user, "lifetime", {});
|
||||
expect(params.mode).toBe("payment");
|
||||
expect(params.subscriptionData).toBeUndefined();
|
||||
expect(params.lineItems[0].price_data?.recurring).toBeUndefined();
|
||||
});
|
||||
|
||||
test("only the weekly pass ever carries a trial", () => {
|
||||
const plans = ["weekly", "annual", "lifetime"] as const;
|
||||
for (const plan of plans) {
|
||||
const params = buildCheckoutParams(user, plan, {});
|
||||
const trial = params.subscriptionData?.trial_period_days;
|
||||
expect(trial).toBe(plan === "weekly" ? 3 : undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("ServerPricing — metadata & reference", () => {
|
||||
test("metadata always carries the resolved plan and the user id", () => {
|
||||
for (const plan of ["weekly", "annual", "lifetime"]) {
|
||||
const params = buildCheckoutParams(user, plan, {});
|
||||
expect(params.metadata.plan).toBe(plan);
|
||||
expect(params.metadata.userId).toBe(user.id);
|
||||
expect(params.clientReferenceId).toBe(user.id);
|
||||
}
|
||||
});
|
||||
|
||||
test("the fallback plan keeps the authenticated user's id attached", () => {
|
||||
const params = buildCheckoutParams(user, "hacker-plan", {});
|
||||
expect(params.metadata.plan).toBe("annual");
|
||||
expect(params.metadata.userId).toBe(user.id);
|
||||
expect(params.clientReferenceId).toBe(user.id);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user