Skip to content

Commit 8e31469

Browse files
committed
Plans to tiered subscription. Don't store plan name/tier in db
1 parent 00af124 commit 8e31469

File tree

5 files changed

+17
-81
lines changed

5 files changed

+17
-81
lines changed
Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
1-
export const PLAN_NAMES = ['pro'] as const
2-
export type PlanName = (typeof PLAN_NAMES)[number]
1+
export const SUBSCRIPTION_DISPLAY_NAME = 'Flex' as const
32

4-
export interface PlanConfig {
5-
name: PlanName
6-
displayName: string
3+
export interface TierConfig {
74
monthlyPrice: number
85
creditsPerBlock: number
96
blockDurationHours: number
107
weeklyCreditsLimit: number
118
}
129

13-
export const PLANS = {
14-
pro: {
15-
name: 'pro',
16-
displayName: 'Pro',
10+
export const SUBSCRIPTION_TIERS = {
11+
200: {
1712
monthlyPrice: 200,
1813
creditsPerBlock: 1250,
1914
blockDurationHours: 5,
20-
weeklyCreditsLimit: 15000,
15+
weeklyCreditsLimit: 12500,
2116
},
22-
} as const satisfies Record<PlanName, PlanConfig>
17+
} as const satisfies Record<number, TierConfig>
2318

24-
export function isPlanName(name: string): name is PlanName {
25-
return (PLAN_NAMES as readonly string[]).includes(name)
26-
}
19+
export const DEFAULT_TIER = SUBSCRIPTION_TIERS[200]

packages/billing/src/subscription-webhooks.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { trackEvent } from '@codebuff/common/analytics'
22
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
3-
import { PLANS } from '@codebuff/common/constants/subscription-plans'
43
import db from '@codebuff/internal/db'
54
import * as schema from '@codebuff/internal/db/schema'
6-
import { env } from '@codebuff/internal/env'
75
import { stripeServer } from '@codebuff/internal/util/stripe'
86
import { eq } from 'drizzle-orm'
97

108
import { handleSubscribe } from './subscription'
119

1210
import type { Logger } from '@codebuff/common/types/contracts/logger'
13-
import type { PlanConfig } from '@codebuff/common/constants/subscription-plans'
1411
import type Stripe from 'stripe'
1512

1613
type SubscriptionStatus = (typeof schema.subscriptionStatusEnum.enumValues)[number]
@@ -38,22 +35,6 @@ async function getUserIdByCustomerId(
3835
return userRecord[0]?.id ?? null
3936
}
4037

41-
/**
42-
* Resolves a PlanConfig from a Stripe price ID.
43-
* Compares against the configured env var for each plan.
44-
*/
45-
function getPlanFromPriceId(priceId: string): PlanConfig {
46-
if (!env.STRIPE_SUBSCRIPTION_200_PRICE_ID) {
47-
throw new Error(
48-
'STRIPE_SUBSCRIPTION_200_PRICE_ID env var is not configured',
49-
)
50-
}
51-
if (env.STRIPE_SUBSCRIPTION_200_PRICE_ID === priceId) {
52-
return PLANS.pro
53-
}
54-
throw new Error(`Unknown subscription price ID: ${priceId}`)
55-
}
56-
5738
// ---------------------------------------------------------------------------
5839
// invoice.paid
5940
// ---------------------------------------------------------------------------
@@ -100,17 +81,6 @@ export async function handleSubscriptionInvoicePaid(params: {
10081
return
10182
}
10283

103-
let plan: PlanConfig
104-
try {
105-
plan = getPlanFromPriceId(priceId)
106-
} catch {
107-
logger.warn(
108-
{ subscriptionId, priceId },
109-
'Subscription invoice for unrecognised price — skipping',
110-
)
111-
return
112-
}
113-
11484
// Look up the user for this customer
11585
const userId = await getUserIdByCustomerId(customerId)
11686

@@ -138,7 +108,6 @@ export async function handleSubscriptionInvoicePaid(params: {
138108
stripe_customer_id: customerId,
139109
user_id: userId,
140110
stripe_price_id: priceId,
141-
plan_name: plan.name,
142111
status: 'active',
143112
billing_period_start: new Date(stripeSub.current_period_start * 1000),
144113
billing_period_end: new Date(stripeSub.current_period_end * 1000),
@@ -150,7 +119,6 @@ export async function handleSubscriptionInvoicePaid(params: {
150119
status: 'active',
151120
...(userId ? { user_id: userId } : {}),
152121
stripe_price_id: priceId,
153-
plan_name: plan.name,
154122
billing_period_start: new Date(
155123
stripeSub.current_period_start * 1000,
156124
),
@@ -164,7 +132,6 @@ export async function handleSubscriptionInvoicePaid(params: {
164132
{
165133
subscriptionId,
166134
customerId,
167-
planName: plan.name,
168135
billingReason: invoice.billing_reason,
169136
},
170137
'Processed subscription invoice.paid',
@@ -243,18 +210,6 @@ export async function handleSubscriptionUpdated(params: {
243210
return
244211
}
245212

246-
let planName: string
247-
try {
248-
const plan = getPlanFromPriceId(priceId)
249-
planName = plan.name
250-
} catch {
251-
logger.warn(
252-
{ subscriptionId, priceId },
253-
'Subscription updated with unrecognised price — skipping',
254-
)
255-
return
256-
}
257-
258213
const customerId =
259214
typeof stripeSubscription.customer === 'string'
260215
? stripeSubscription.customer
@@ -272,7 +227,6 @@ export async function handleSubscriptionUpdated(params: {
272227
stripe_customer_id: customerId,
273228
user_id: userId,
274229
stripe_price_id: priceId,
275-
plan_name: planName,
276230
status,
277231
cancel_at_period_end: stripeSubscription.cancel_at_period_end,
278232
billing_period_start: new Date(
@@ -287,7 +241,6 @@ export async function handleSubscriptionUpdated(params: {
287241
set: {
288242
...(userId ? { user_id: userId } : {}),
289243
stripe_price_id: priceId,
290-
plan_name: planName,
291244
status,
292245
cancel_at_period_end: stripeSubscription.cancel_at_period_end,
293246
billing_period_start: new Date(
@@ -303,7 +256,6 @@ export async function handleSubscriptionUpdated(params: {
303256
logger.info(
304257
{
305258
subscriptionId,
306-
planName,
307259
cancelAtPeriodEnd: stripeSubscription.cancel_at_period_end,
308260
},
309261
'Processed subscription update',

packages/billing/src/subscription.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { trackEvent } from '@codebuff/common/analytics'
22
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
33
import { GRANT_PRIORITIES } from '@codebuff/common/constants/grant-priorities'
44
import {
5-
PLANS,
6-
isPlanName,
5+
DEFAULT_TIER,
6+
SUBSCRIPTION_DISPLAY_NAME,
77
} from '@codebuff/common/constants/subscription-plans'
88
import db from '@codebuff/internal/db'
99
import * as schema from '@codebuff/internal/db/schema'
@@ -131,15 +131,14 @@ export function getWeekEnd(
131131

132132
/**
133133
* Resolves the effective subscription limits for a user.
134-
* Checks `limit_override` first, then falls back to the plan constants.
134+
* Checks `limit_override` first, then falls back to the default tier constants.
135135
*/
136136
export async function getSubscriptionLimits(params: {
137137
userId: string
138-
planName: string
139138
logger: Logger
140139
conn?: DbConn
141140
}): Promise<SubscriptionLimits> {
142-
const { userId, planName, logger, conn = db } = params
141+
const { userId, logger, conn = db } = params
143142

144143
const overrides = await conn
145144
.select()
@@ -160,15 +159,10 @@ export async function getSubscriptionLimits(params: {
160159
}
161160
}
162161

163-
if (!isPlanName(planName)) {
164-
throw new Error(`Unknown plan name: ${planName}`)
165-
}
166-
167-
const plan = PLANS[planName]
168162
return {
169-
creditsPerBlock: plan.creditsPerBlock,
170-
blockDurationHours: plan.blockDurationHours,
171-
weeklyCreditsLimit: plan.weeklyCreditsLimit,
163+
creditsPerBlock: DEFAULT_TIER.creditsPerBlock,
164+
blockDurationHours: DEFAULT_TIER.blockDurationHours,
165+
weeklyCreditsLimit: DEFAULT_TIER.weeklyCreditsLimit,
172166
}
173167
}
174168

@@ -282,7 +276,6 @@ export async function ensureActiveBlockGrant(params: {
282276
// 2. Resolve limits
283277
const limits = await getSubscriptionLimits({
284278
userId,
285-
planName: subscription.plan_name,
286279
logger,
287280
conn: tx,
288281
})
@@ -331,7 +324,7 @@ export async function ensureActiveBlockGrant(params: {
331324
balance: limits.creditsPerBlock,
332325
priority: GRANT_PRIORITIES.subscription,
333326
expires_at: expiresAt,
334-
description: `${subscription.plan_name} block (${limits.blockDurationHours}h)`,
327+
description: `${SUBSCRIPTION_DISPLAY_NAME} block (${limits.blockDurationHours}h)`,
335328
})
336329
.onConflictDoNothing({ target: schema.creditLedger.operation_id })
337330
.returning()
@@ -404,7 +397,6 @@ export async function checkRateLimit(params: {
404397

405398
const limits = await getSubscriptionLimits({
406399
userId,
407-
planName: subscription.plan_name,
408400
logger,
409401
})
410402

packages/internal/src/db/schema.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,6 @@ export const subscription = pgTable(
463463
stripe_customer_id: text('stripe_customer_id').notNull(),
464464
user_id: text('user_id').references(() => user.id, { onDelete: 'cascade' }),
465465
stripe_price_id: text('stripe_price_id').notNull(),
466-
plan_name: text('plan_name').notNull(),
467466
status: subscriptionStatusEnum('status').notNull().default('active'),
468467
billing_period_start: timestamp('billing_period_start', {
469468
mode: 'date',

web/src/app/profile/components/usage-display.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ const grantTypeInfo: Record<
5858
text: 'text-indigo-600 dark:text-indigo-400',
5959
gradient: 'from-indigo-500/70 to-indigo-600/70',
6060
icon: <Star className="h-4 w-4" />,
61-
label: 'Pro Subscription',
62-
description: 'Credits from your Pro plan',
61+
label: 'Flex',
62+
description: 'Credits from your Flex subscription',
6363
},
6464
referral: {
6565
bg: 'bg-green-500',

0 commit comments

Comments
 (0)