Pro/business

This commit is contained in:
Timo Knuth
2026-07-06 00:18:06 +02:00
parent 0b9c8d2a8f
commit 91bd940edc
10 changed files with 384 additions and 40 deletions

View File

@@ -34,13 +34,14 @@ export async function POST(request: NextRequest) {
}
// Get user with subscription info
const user = await db.user.findUnique({
where: { id: userId },
select: {
stripeSubscriptionId: true,
plan: true,
},
});
const user = await db.user.findUnique({
where: { id: userId },
select: {
stripeSubscriptionId: true,
stripeCurrentPeriodEnd: true,
plan: true,
},
});
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
@@ -62,27 +63,34 @@ export async function POST(request: NextRequest) {
stripeCurrentPeriodEnd: null,
},
});
await scoreUserLifecycle(userId, 'subscription_changed');
await scoreUserLifecycle(userId, 'subscription_deleted');
return NextResponse.json({ success: true });
}
// Cancel the Stripe subscription
await stripe.subscriptions.cancel(user.stripeSubscriptionId);
// Update user plan to FREE
// Schedule cancellation at the end of the paid period so paid features stay active.
const subscription: any = await stripe.subscriptions.update(user.stripeSubscriptionId, {
cancel_at_period_end: true,
});
const periodEndTimestamp = subscription.current_period_end
|| subscription.currentPeriodEnd
|| subscription.billing_cycle_anchor;
const currentPeriodEnd = periodEndTimestamp
? new Date(periodEndTimestamp * 1000)
: user.stripeCurrentPeriodEnd;
// Keep the paid plan locally until Stripe sends customer.subscription.deleted.
await db.user.update({
where: { id: userId },
data: {
plan: 'FREE',
stripeSubscriptionId: null,
stripePriceId: null,
stripeCurrentPeriodEnd: null,
stripeCurrentPeriodEnd: currentPeriodEnd,
},
});
await scoreUserLifecycle(userId, 'subscription_changed');
await scoreUserLifecycle(userId, 'subscription_canceled_at_period_end');
return NextResponse.json({ success: true });
return NextResponse.json({ success: true, currentPeriodEnd });
} catch (error) {
console.error('Error canceling subscription:', error);
return NextResponse.json(

View File

@@ -48,7 +48,7 @@ export async function POST(request: NextRequest) {
},
});
await scoreUserLifecycle(user.id, 'subscription_changed');
await scoreUserLifecycle(user.id, 'subscription_deleted');
return NextResponse.json({
success: true,
@@ -100,7 +100,7 @@ export async function POST(request: NextRequest) {
},
});
await scoreUserLifecycle(user.id, 'subscription_changed');
await scoreUserLifecycle(user.id, 'subscription_synced');
return NextResponse.json({
success: true,

View File

@@ -73,7 +73,7 @@ export async function POST(request: NextRequest) {
},
});
await scoreUserLifecycle(user.id, 'subscription_changed');
await scoreUserLifecycle(user.id, 'subscription_created');
return NextResponse.json({
success: true,

View File

@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { headers } from 'next/headers';
import { stripe } from '@/lib/stripe';
import { NextRequest, NextResponse } from 'next/server';
import { headers } from 'next/headers';
import { getPlanFromStripePriceId, stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
import Stripe from 'stripe';
import { sendConversionEvent } from '@/lib/metaConversions';
@@ -63,7 +63,7 @@ export async function POST(request: NextRequest) {
},
});
await scoreUserLifecycle(updatedUser.id, 'subscription_changed');
await scoreUserLifecycle(updatedUser.id, 'subscription_created');
// Meta CAPI — Purchase event
const amountCents = session.amount_total ?? 0;
@@ -98,10 +98,11 @@ export async function POST(request: NextRequest) {
await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
data: {
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: currentPeriodEnd,
},
data: {
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: currentPeriodEnd,
plan: getPlanFromStripePriceId(subscription.items.data[0].price.id) ?? undefined,
},
});
const updated = await db.user.findUnique({
@@ -109,7 +110,10 @@ export async function POST(request: NextRequest) {
select: { id: true },
});
if (updated?.id) {
await scoreUserLifecycle(updated.id, 'subscription_changed');
await scoreUserLifecycle(
updated.id,
subscription.cancel_at_period_end ? 'subscription_canceled_at_period_end' : 'subscription_updated'
);
}
break;
}
@@ -129,7 +133,7 @@ export async function POST(request: NextRequest) {
},
});
await scoreUserLifecycle(updatedUser.id, 'subscription_changed');
await scoreUserLifecycle(updatedUser.id, 'subscription_deleted');
break;
}
}