stack/packages/stack-shared/src/plans.ts
Aman Ganapathy e9886bc45a
[Fix] [Refactor] Implement Base Settings for Stack-Auth Plans and Move Metadata from Stripe Webhook Event to Table (#1214)
### Context
We're looking at implementing plan pricing. While doing so, we
encountered a problem with Stripe.
**Problem:** when we run a stripe operation (purchase), the product info
is encoded as part of the stripe metadata request. Stripe encodes
metadata as key-value pairs, and the [value has a limit of 500
chars](https://docs.stripe.com/metadata#data). We do this because once
we run the stripe operation, stripe fires a webhook event which is
caught by our stripe webhook handler syncStripeSubscriptions. This gets
the stripe metadata info from the event and then updates our db in
prisma.

### Summary of Changes
We add a `ProductVersion` table and only pass the `productVersionId` via
stripe metadata instead of the whole product json. This
`productVersionId` is created by hashing the `productJson`. Since the
same product may be ordered differently without being intrinsically
different, we add a helper function for ensuring a canonical order to
the json. We also pass tenancy id and product id to the table.
Since there are existing subscriptions which used to pass the
productJson via metadata, we ensure backwards compatibility.
2026-02-23 22:09:27 -08:00

66 lines
1.5 KiB
TypeScript

/**
* Plan configuration for Stack Auth pricing tiers.
*
* This file defines the limits for each plan and the item IDs used to track them.
* Import these constants in seed.ts and backend code for limit enforcement.
*/
export const UNLIMITED = 1_000_000_000;
/**
* Item IDs used across the codebase for tracking plan limits.
*/
export const ITEM_IDS = {
seats: "dashboard_admins",
authUsers: "auth_users",
emailsPerMonth: "emails_per_month",
analyticsTimeoutSeconds: "analytics_timeout_seconds",
analyticsEvents: "analytics_events",
} as const;
export type ItemId = typeof ITEM_IDS[keyof typeof ITEM_IDS];
/**
* The offerings/limits included in a plan.
*/
export type PlanProductOfferings = {
seats: number,
authUsers: number,
emailsPerMonth: number,
analyticsTimeoutSeconds: number,
analyticsEvents: number,
};
/**
* Plan limits by plan ID.
*/
export const PLAN_LIMITS: {
free: PlanProductOfferings,
team: PlanProductOfferings,
growth: PlanProductOfferings,
} = {
free: {
seats: 1,
authUsers: 10_000,
emailsPerMonth: 1_000,
analyticsTimeoutSeconds: 10,
analyticsEvents: 100_000,
},
team: {
seats: 4,
authUsers: 50_000,
emailsPerMonth: 25_000,
analyticsTimeoutSeconds: 60,
analyticsEvents: 500_000,
},
growth: {
seats: UNLIMITED,
authUsers: UNLIMITED,
emailsPerMonth: 25_000,
analyticsTimeoutSeconds: 300,
analyticsEvents: 1_000_000,
},
};
export type PlanId = keyof typeof PLAN_LIMITS;