Skip to content

Commit 4b1be51

Browse files
bidahclaude
andcommitted
Fix subscribed users incorrectly blocked by message limit
Three issues caused paid users to see "Message Limit Reached": 1. FREE_PLAN_MESSAGE_LIMIT was 0, so every fallback path (DB error, missing subscription, wrong plan name) blocked all users immediately. Changed to 10 to give free users a trial and prevent cascading failures. 2. Polar webhook stored raw product names (e.g. "Inti Computer - Starter") as currentPlan instead of normalized slugs ("start"). The limit lookup never matched, defaulting to the free limit. Added normalizePlanName() to fuzzy-match product names to plan slugs. 3. getUserMessageUsage() error handler returned remainingMessages: 0, silently blocking users on any DB error. Now fails open with 1 message allowed so transient errors don't lock out paying customers. Also created missing subscriptions/prompt_messages tables and seeded subscription records for existing paid users in production DB. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7c76a83 commit 4b1be51

4 files changed

Lines changed: 33 additions & 25 deletions

File tree

‎apps/web/app/(app)/api/webhooks/polar/route.ts‎

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,23 @@ function getNextResetDate(): Date {
1111
return nextMonth
1212
}
1313

14+
// Normalize Polar product names to our plan slugs
15+
// e.g. "Inti Computer - Starter" -> "start", "Pro Plan" -> "pro"
16+
function normalizePlanName(productName: string): string {
17+
const name = productName?.toLowerCase() || ''
18+
if (name.includes('senior')) return 'senior'
19+
if (name.includes('pro')) return 'pro'
20+
if (name.includes('start') || name.includes('starter')) return 'start'
21+
return 'free'
22+
}
23+
1424
// Map plan names to message limits
1525
function getMessageLimitForPlan(planName: string): string {
16-
switch (planName?.toLowerCase()) {
17-
case 'start':
18-
return CONFIG.PAID_PLAN_LIMITS.start.toString()
19-
case 'pro':
20-
return CONFIG.PAID_PLAN_LIMITS.pro.toString()
21-
case 'senior':
22-
return CONFIG.PAID_PLAN_LIMITS.senior.toString()
23-
default:
24-
return CONFIG.FREE_PLAN_MESSAGE_LIMIT.toString() // free tier
25-
}
26+
const normalized = normalizePlanName(planName)
27+
if (normalized === 'senior') return CONFIG.PAID_PLAN_LIMITS.senior.toString()
28+
if (normalized === 'pro') return CONFIG.PAID_PLAN_LIMITS.pro.toString()
29+
if (normalized === 'start') return CONFIG.PAID_PLAN_LIMITS.start.toString()
30+
return CONFIG.FREE_PLAN_MESSAGE_LIMIT.toString()
2631
}
2732

2833
// Export the route as a raw body handler to preserve the exact request body for signature verification
@@ -189,10 +194,11 @@ export async function POST(request: NextRequest) {
189194

190195
if (userData.length > 0) {
191196
const userId = userData[0].id
192-
const planName = checkoutData.product?.name || 'free'
193-
194-
console.log(`[Polar Webhook] Updating subscription for user ${userId} to plan ${planName}`)
195-
197+
const rawPlanName = checkoutData.product?.name || 'free'
198+
const planName = normalizePlanName(rawPlanName)
199+
200+
console.log(`[Polar Webhook] Updating subscription for user ${userId} to plan ${planName} (raw: ${rawPlanName})`)
201+
196202
// Check if subscription exists
197203
const existingSub = await db
198204
.select()
@@ -202,7 +208,7 @@ export async function POST(request: NextRequest) {
202208

203209
const subscriptionData = {
204210
customerId: checkoutData.customer_id,
205-
currentPlan: planName.toLowerCase(),
211+
currentPlan: planName,
206212
checkoutId: checkoutData.id,
207213
productId: checkoutData.product_id,
208214
status: 'active',
@@ -263,10 +269,11 @@ export async function POST(request: NextRequest) {
263269

264270
if (userData && userData.length > 0) {
265271
const userId = userData[0].id
266-
const planName = subscriptionData.product?.name || 'free'
267-
268-
console.log(`[Polar Webhook] Updating subscription for user ${userId}`)
269-
272+
const rawPlanName = subscriptionData.product?.name || 'free'
273+
const planName = normalizePlanName(rawPlanName)
274+
275+
console.log(`[Polar Webhook] Updating subscription for user ${userId} to plan ${planName} (raw: ${rawPlanName})`)
276+
270277
// Check if subscription exists
271278
const existingSub = await db
272279
.select()
@@ -276,7 +283,7 @@ export async function POST(request: NextRequest) {
276283

277284
const subData = {
278285
customerId: subscriptionData.customer_id,
279-
currentPlan: planName.toLowerCase(),
286+
currentPlan: planName,
280287
subscriptionId: subscriptionData.id,
281288
productId: subscriptionData.product_id,
282289
status: subscriptionData.status,

‎packages/config/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const CONFIG = {
77
* Free plan message limit per month
88
* This is the default number of messages a user can send without a subscription
99
*/
10-
FREE_PLAN_MESSAGE_LIMIT: 0,
10+
FREE_PLAN_MESSAGE_LIMIT: 10,
1111

1212
/**
1313
* Default paid plan limits (can be overridden by subscription data)

‎packages/payments/src/lib/config.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const PAYMENTS_CONFIG = {
88
/**
99
* Free plan message limit per month
1010
*/
11-
FREE_PLAN_MESSAGE_LIMIT: 0,
11+
FREE_PLAN_MESSAGE_LIMIT: 10,
1212

1313
/**
1414
* Paid plan limits by plan name

‎packages/payments/src/lib/message-usage.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ export async function getUserMessageUsage(userId: string): Promise<MessageUsage>
5151
}
5252
} catch (error) {
5353
console.error('[Payments] Error getting user message usage:', error)
54-
// Return default free tier limits on error
54+
// On DB error, allow the message through rather than blocking the user.
55+
// A missing table or transient DB issue should not prevent paid users from chatting.
5556
return {
56-
messageLimit: PAYMENTS_CONFIG.FREE_PLAN_MESSAGE_LIMIT,
57+
messageLimit: 1,
5758
usageCount: 0,
58-
remainingMessages: PAYMENTS_CONFIG.FREE_PLAN_MESSAGE_LIMIT,
59+
remainingMessages: 1,
5960
hasActiveSubscription: false,
6061
currentPlan: 'free',
6162
currentMonth: getCurrentMonth(),

0 commit comments

Comments
 (0)