From be7a8d78d030fef3902135e78b41d0ec0f4c86e5 Mon Sep 17 00:00:00 2001 From: mantrakp04 Date: Wed, 29 Apr 2026 21:31:07 -0700 Subject: [PATCH 1/2] fix preview dummy payments customer types --- .claude/CLAUDE-KNOWLEDGE.md | 3 +++ apps/backend/src/lib/seed-dummy-data.test.ts | 23 ++++++++++++++++++ apps/backend/src/lib/seed-dummy-data.ts | 25 ++++++++++---------- 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 apps/backend/src/lib/seed-dummy-data.test.ts diff --git a/.claude/CLAUDE-KNOWLEDGE.md b/.claude/CLAUDE-KNOWLEDGE.md index 17a34543b9..ac6d53b48c 100644 --- a/.claude/CLAUDE-KNOWLEDGE.md +++ b/.claude/CLAUDE-KNOWLEDGE.md @@ -361,3 +361,6 @@ A: Invalid `tools` entries are rejected by `requestBodySchema` in `apps/backend/ ## Q: Why did the internal metrics E2E snapshots need to change in April 2026? A: The `/api/v1/internal/metrics` response now intentionally includes `analytics_overview.daily_anonymous_visitors_fallback`, `analytics_overview.anonymous_visitors_fallback`, and `active_users_by_country`. Those additions are reflected in `packages/stack-shared/src/interface/admin-metrics.ts` and the backend route, so the E2E snapshots must include them instead of treating them as regressions. + +## Q: Why can environment config override writes fail with a product/product-line customer type warning after creating a preview project? +A: The environment override endpoint validates the new environment override against the rendered branch config. Preview dummy payments data must therefore be internally coherent: products assigned to a product line need the same `customerType` as that product line, otherwise unrelated environment patches can fail with warnings like `Product "growth" has customer type "user" but its product line "workspace" has customer type "team"`. diff --git a/apps/backend/src/lib/seed-dummy-data.test.ts b/apps/backend/src/lib/seed-dummy-data.test.ts new file mode 100644 index 0000000000..d8ad5ced83 --- /dev/null +++ b/apps/backend/src/lib/seed-dummy-data.test.ts @@ -0,0 +1,23 @@ +import { branchConfigSchema, getConfigOverrideErrors, getIncompleteConfigWarnings } from "@stackframe/stack-shared/dist/config/schema"; +import { describe, expect, it } from "vitest"; +import { buildDummyPaymentsSetup } from "./seed-dummy-data"; + +describe("dummy payments seed config", () => { + it("is valid branch payments config", async () => { + const { paymentsBranchOverride } = buildDummyPaymentsSetup(); + const branchConfigOverride = { payments: paymentsBranchOverride }; + + expect(await getConfigOverrideErrors(branchConfigSchema, branchConfigOverride)).toMatchInlineSnapshot(` + { + "data": null, + "status": "ok", + } + `); + expect(await getIncompleteConfigWarnings(branchConfigSchema, branchConfigOverride)).toMatchInlineSnapshot(` + { + "data": null, + "status": "ok", + } + `); + }); +}); diff --git a/apps/backend/src/lib/seed-dummy-data.ts b/apps/backend/src/lib/seed-dummy-data.ts index a9ad484273..5a5b707f43 100644 --- a/apps/backend/src/lib/seed-dummy-data.ts +++ b/apps/backend/src/lib/seed-dummy-data.ts @@ -11,6 +11,7 @@ import { getPrismaClientForTenancy, globalPrismaClient, type PrismaClientTransac import { ALL_APPS } from '@stackframe/stack-shared/dist/apps/apps-config'; import { DEFAULT_EMAIL_THEME_ID } from '@stackframe/stack-shared/dist/helpers/emails'; import { type AdminUserProjectsCrud, type ProjectsCrud } from '@stackframe/stack-shared/dist/interface/crud/projects'; +import { type Config } from '@stackframe/stack-shared/dist/config/format'; import { DayInterval } from '@stackframe/stack-shared/dist/utils/dates'; import { getEnvVariable } from '@stackframe/stack-shared/dist/utils/env'; import { throwErr } from '@stackframe/stack-shared/dist/utils/errors'; @@ -95,9 +96,9 @@ type SeedDummyUsersOptions = { }; type PaymentsSetup = { - paymentsProducts: Record, - paymentsBranchOverride: Record, - paymentsEnvironmentOverride: Record, + paymentsProducts: Record, + paymentsBranchOverride: Config, + paymentsEnvironmentOverride: Config, }; type TransactionsSeedOptions = { @@ -707,16 +708,16 @@ async function seedDummyUsers(options: SeedDummyUsersOptions): Promise = { + const paymentsProducts: Record = { 'starter': { displayName: 'Starter', productLineId: 'workspace', - customerType: 'user', + customerType: 'team', serverOnly: false, stackable: false, freeTrial: twoWeekInterval as any, @@ -744,7 +745,7 @@ function buildDummyPaymentsSetup(): PaymentsSetup { 'growth': { displayName: 'Growth', productLineId: 'workspace', - customerType: 'user', + customerType: 'team', serverOnly: false, stackable: false, prices: { @@ -780,7 +781,7 @@ function buildDummyPaymentsSetup(): PaymentsSetup { 'regression-addon': { displayName: 'Regression Add-on', productLineId: 'add_ons', - customerType: 'user', + customerType: 'team', serverOnly: false, stackable: true, prices: { @@ -818,19 +819,19 @@ function buildDummyPaymentsSetup(): PaymentsSetup { items: { studio_seats: { displayName: 'Studio Seats', - customerType: 'user', + customerType: 'team', }, review_passes: { displayName: 'Reviewer Passes', - customerType: 'user', + customerType: 'team', }, automation_minutes: { displayName: 'Automation Minutes', - customerType: 'user', + customerType: 'team', }, snapshot_credits: { displayName: 'Snapshot Credits', - customerType: 'user', + customerType: 'team', }, }, products: paymentsProducts, From 9c846320e6497b0b2348e1ce69364b6eaf4028ff Mon Sep 17 00:00:00 2001 From: mantrakp04 Date: Thu, 30 Apr 2026 09:38:11 -0700 Subject: [PATCH 2/2] Refactor payments setup types and improve dummy data seeding - Updated `PaymentsSetup` and `TransactionsSeedOptions` types to use a more specific `PaymentsProducts` type instead of a generic `Record`. - Adjusted the seeding logic for dummy transactions to correctly resolve customer IDs for teams instead of users. - Enhanced the dummy data generation for payments to ensure better alignment with the updated type definitions. --- apps/backend/src/lib/seed-dummy-data.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/backend/src/lib/seed-dummy-data.ts b/apps/backend/src/lib/seed-dummy-data.ts index 5a5b707f43..e117402eb7 100644 --- a/apps/backend/src/lib/seed-dummy-data.ts +++ b/apps/backend/src/lib/seed-dummy-data.ts @@ -95,8 +95,12 @@ type SeedDummyUsersOptions = { teamNameToId: Map, }; +type PaymentsProducts = { + [productId: string]: Config | undefined, +}; + type PaymentsSetup = { - paymentsProducts: Record, + paymentsProducts: PaymentsProducts, paymentsBranchOverride: Config, paymentsEnvironmentOverride: Config, }; @@ -105,8 +109,7 @@ type TransactionsSeedOptions = { prisma: PrismaClientTransaction, tenancyId: string, teamNameToId: Map, - userEmailToId: Map, - paymentsProducts: Record, + paymentsProducts: PaymentsProducts, }; type EmailSeedOptions = { @@ -713,7 +716,7 @@ export function buildDummyPaymentsSetup(): PaymentsSetup { const yearlyInterval: DayInterval = [1, 'year']; const twoWeekInterval: DayInterval = [2, 'week']; - const paymentsProducts: Record = { + const paymentsProducts: PaymentsProducts = { 'starter': { displayName: 'Starter', productLineId: 'workspace', @@ -896,12 +899,10 @@ async function seedDummyTransactions(options: TransactionsSeedOptions) { prisma, tenancyId, teamNameToId, - userEmailToId, paymentsProducts, } = options; const resolveTeamId = (teamName: string) => teamNameToId.get(teamName) ?? throwErr(`Unknown dummy project team ${teamName}`); - const resolveUserId = (email: string) => userEmailToId.get(email) ?? throwErr(`Unknown dummy project user ${email}`); const resolveProduct = (productId: string): Prisma.InputJsonValue => { const product = paymentsProducts[productId]; if (!product) { @@ -945,8 +946,8 @@ async function seedDummyTransactions(options: TransactionsSeedOptions) { }, { id: DUMMY_SEED_IDS.subscriptions.mateoGrowthAnnual, - customerType: CustomerType.USER, - customerId: resolveUserId('mateo.silva@dummy.dev'), + customerType: CustomerType.TEAM, + customerId: resolveTeamId('Growth Loop'), productId: 'growth', priceId: 'annual', product: resolveProduct('growth'), @@ -1090,8 +1091,8 @@ async function seedDummyTransactions(options: TransactionsSeedOptions) { const oneTimePurchaseSeeds: OneTimePurchaseSeed[] = [ { id: DUMMY_SEED_IDS.oneTimePurchases.ameliaSeatPack, - customerType: CustomerType.USER, - customerId: resolveUserId('amelia.chen@dummy.dev'), + customerType: CustomerType.TEAM, + customerId: resolveTeamId('Design Systems Lab'), productId: 'starter', priceId: 'monthly', product: resolveProduct('starter'), @@ -2095,7 +2096,6 @@ export async function seedDummyProject(options: SeedDummyProjectOptions): Promis prisma: dummyPrisma, tenancyId: dummyTenancy.id, teamNameToId, - userEmailToId, paymentsProducts, });