Skip to content

Commit ae29041

Browse files
committed
Fix subscription CTA flow and harden billing consistency
1 parent d819549 commit ae29041

File tree

5 files changed

+29
-102
lines changed

5 files changed

+29
-102
lines changed

src/app/(dashboard)/home/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ export default async function Home({
7474

7575
// Get sort parameters for both sections
7676
const baseSort = (params.baseSort as SortOption) || 'createdAt';
77-
const baseDirection = (params.baseDirection as SortDirection) || 'asc';
77+
const baseDirection = (params.baseDirection as SortDirection) || 'desc';
7878
const tailoredSort = (params.tailoredSort as SortOption) || 'createdAt';
79-
const tailoredDirection = (params.tailoredDirection as SortDirection) || 'asc';
79+
const tailoredDirection = (params.tailoredDirection as SortDirection) || 'desc';
8080

8181
// Sort function
8282
function sortResumes(resumes: ResumeSummary[], sort: SortOption, direction: SortDirection) {
@@ -89,8 +89,8 @@ export default async function Home({
8989
return modifier * ((a.target_role || '').localeCompare(b.target_role || '') || 0);
9090
case 'createdAt':
9191
default:
92-
return modifier * (new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
93-
92+
return modifier * (new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
93+
9494
}
9595
});
9696
}

src/app/(dashboard)/resumes/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default async function ResumesPage({
3737
return modifier * (a.target_role?.localeCompare(b.target_role || '') || 0);
3838
case 'createdAt':
3939
default:
40-
return modifier * (new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
40+
return modifier * (new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
4141
}
4242
});
4343

src/app/api/webhooks/stripe/route.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,7 @@ async function handleSubscriptionChange(
7575
planId: subscriptionData.planId,
7676
data: subscriptionData
7777
});
78-
79-
// Log but don't throw the error to prevent webhook failure response
80-
// This allows the webhook to acknowledge receipt even if DB update fails
81-
console.error('Continuing webhook processing despite error...');
78+
throw error;
8279
}
8380
}
8481

@@ -244,27 +241,18 @@ export async function POST(req: Request) {
244241
status: subscription.status
245242
});
246243

247-
try {
248-
await handleSubscriptionChange(
249-
subscription.customer as string,
250-
{
251-
subscriptionId: subscription.id,
252-
planId: 'free',
253-
status: 'canceled',
254-
currentPeriodEnd: null,
255-
trialEnd: null,
256-
cancelAtPeriodEnd: false
257-
}
258-
);
259-
console.log('✅ Subscription deletion processed successfully');
260-
} catch (error) {
261-
console.error('❌ Failed to process subscription deletion:', {
262-
error: error instanceof Error ? error.message : 'Unknown error',
244+
await handleSubscriptionChange(
245+
subscription.customer as string,
246+
{
263247
subscriptionId: subscription.id,
264-
customerId: subscription.customer
265-
});
266-
// Continue processing the webhook even if this fails
267-
}
248+
planId: 'free',
249+
status: 'canceled',
250+
currentPeriodEnd: null,
251+
trialEnd: null,
252+
cancelAtPeriodEnd: false
253+
}
254+
);
255+
console.log('✅ Subscription deletion processed successfully');
268256
break;
269257
}
270258

src/components/settings/subscription-section.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { useState, useEffect } from 'react';
4+
import { useRouter } from 'next/navigation';
45
import { Button } from "@/components/ui/button"
56
import { Badge } from "@/components/ui/badge"
67
import { Sparkles, Star, Clock, Zap, ArrowRight, Crown, Shield, Check, Users, TrendingUp } from "lucide-react"
@@ -13,6 +14,7 @@ export function SubscriptionSection() {
1314
const [isLoading, setIsLoading] = useState(false);
1415
const [profile, setProfile] = useState<SubscriptionSnapshot | null>(null);
1516
const [isLoadingProfile, setIsLoadingProfile] = useState(true);
17+
const router = useRouter();
1618

1719
useEffect(() => {
1820
async function fetchSubscriptionStatus() {
@@ -41,7 +43,15 @@ export function SubscriptionSection() {
4143
trialEndLabel,
4244
} = subscriptionAccessState;
4345

44-
const handlePortalSession = async () => {
46+
const handleSubscriptionAction = async () => {
47+
if (!hasProAccess) {
48+
const priceId = process.env.NEXT_PUBLIC_STRIPE_PRO_PRICE_ID;
49+
if (priceId) {
50+
router.push(`/subscription/checkout?price_id=${priceId}`);
51+
}
52+
return;
53+
}
54+
4555
try {
4656
setIsLoading(true);
4757
const result = await createPortalSession();
@@ -289,7 +299,7 @@ export function SubscriptionSection() {
289299

290300
{/* CTA Button */}
291301
<Button
292-
onClick={handlePortalSession}
302+
onClick={handleSubscriptionAction}
293303
disabled={isLoading}
294304
className={cn(
295305
"w-full py-3 font-semibold rounded-lg transition-all duration-300",

src/utils/actions/stripe/actions.ts

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -281,77 +281,6 @@ export async function getSubscriptionStatus() {
281281
return subscription;
282282
}
283283

284-
export async function createCheckoutSession(priceId: string) {
285-
const supabase = await createClient();
286-
287-
const { data: { user }, error: userError } = await supabase.auth.getUser();
288-
289-
if (userError || !user) {
290-
throw new Error('User not authenticated');
291-
}
292-
293-
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
294-
const response = await fetch(`${basePath}/api/create-checkout-session`, {
295-
method: 'POST',
296-
headers: {
297-
'Content-Type': 'application/json',
298-
},
299-
body: JSON.stringify({
300-
priceId,
301-
userId: user.id,
302-
email: user.email,
303-
}),
304-
});
305-
306-
if (!response.ok) {
307-
throw new Error('Failed to create checkout session');
308-
}
309-
310-
const { sessionId } = await response.json();
311-
return { sessionId };
312-
}
313-
314-
export async function cancelSubscription() {
315-
const supabase = await createClient();
316-
317-
const { data: { user }, error: userError } = await supabase.auth.getUser();
318-
319-
if (userError || !user) {
320-
throw new Error('User not authenticated');
321-
}
322-
323-
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || '';
324-
const response = await fetch(`${basePath}/api/cancel-subscription`, {
325-
method: 'POST',
326-
headers: {
327-
'Content-Type': 'application/json',
328-
},
329-
body: JSON.stringify({
330-
userId: user.id,
331-
}),
332-
});
333-
334-
if (!response.ok) {
335-
throw new Error('Failed to cancel subscription');
336-
}
337-
338-
// Update the profile subscription status
339-
const { error: updateError } = await supabase
340-
.from('profiles')
341-
.update({
342-
subscription_status: 'canceled',
343-
})
344-
.eq('user_id', user.id);
345-
346-
if (updateError) {
347-
throw new Error('Failed to update subscription status');
348-
}
349-
350-
revalidatePath('/', 'layout');
351-
revalidatePath('/settings', 'layout');
352-
revalidatePath('/plans', 'layout');
353-
}
354-
355284
export async function checkSubscriptionPlan() {
356285
const supabase = await createClient();
357286
const { data: { user } } = await supabase.auth.getUser();

0 commit comments

Comments
 (0)