Copy overhaul + qr designs

This commit is contained in:
2026-07-27 17:54:59 +02:00
parent 033bc7e29d
commit 70d97aa970
144 changed files with 23107 additions and 1699 deletions

View File

@@ -35,7 +35,16 @@ export async function POST(request: NextRequest) {
}
// Get plan and billing interval from request
const { plan, billingInterval = 'month' } = await request.json();
const { plan, billingInterval = 'month', returnPath } = await request.json();
// Where to send the user after checkout. Used by the in-app upgrade modal so
// people land back on the thing they were building instead of the dashboard.
const safeReturnPath =
typeof returnPath === 'string' &&
returnPath.startsWith('/') &&
!returnPath.startsWith('//')
? returnPath
: null;
if (!plan || !['PRO', 'BUSINESS'].includes(plan)) {
return NextResponse.json(
@@ -114,8 +123,12 @@ export async function POST(request: NextRequest) {
quantity: 1,
},
],
success_url: `${appUrl}/dashboard?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${appUrl}/pricing?canceled=true`,
success_url: safeReturnPath
? `${appUrl}${safeReturnPath}${safeReturnPath.includes('?') ? '&' : '?'}success=true&session_id={CHECKOUT_SESSION_ID}`
: `${appUrl}/dashboard?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: safeReturnPath
? `${appUrl}${safeReturnPath}${safeReturnPath.includes('?') ? '&' : '?'}canceled=true`
: `${appUrl}/pricing?canceled=true`,
metadata: {
userId: user.id,
plan,

View File

@@ -1,10 +1,10 @@
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';
import { scoreUserLifecycle } from '@/lib/revops-server';
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';
import { scoreUserLifecycle } from '@/lib/revops-server';
export async function POST(request: NextRequest) {
const body = await request.text();
@@ -51,21 +51,21 @@ export async function POST(request: NextRequest) {
? new Date(periodEndTimestamp * 1000)
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
const updatedUser = await db.user.update({
where: {
stripeCustomerId: session.customer as string,
const updatedUser = await db.user.update({
where: {
stripeCustomerId: session.customer as string,
},
data: {
stripeSubscriptionId: subscription.id,
stripePriceId: subscription.items.data[0].price.id,
stripeCurrentPeriodEnd: currentPeriodEnd,
plan: (session.metadata?.plan || 'FREE') as any,
},
});
await scoreUserLifecycle(updatedUser.id, 'subscription_created');
},
});
// Meta CAPI — Purchase event
await scoreUserLifecycle(updatedUser.id, 'subscription_created');
// Meta CAPI - Purchase event
const amountCents = session.amount_total ?? 0;
sendConversionEvent({
eventName: 'Purchase',
@@ -95,47 +95,47 @@ export async function POST(request: NextRequest) {
? new Date(periodEndTimestamp * 1000)
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
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({
where: { stripeSubscriptionId: subscription.id },
select: { id: true },
});
if (updated?.id) {
await scoreUserLifecycle(
updated.id,
subscription.cancel_at_period_end ? 'subscription_canceled_at_period_end' : 'subscription_updated'
);
}
break;
}
await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
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({
where: { stripeSubscriptionId: subscription.id },
select: { id: true },
});
if (updated?.id) {
await scoreUserLifecycle(
updated.id,
subscription.cancel_at_period_end ? 'subscription_canceled_at_period_end' : 'subscription_updated'
);
}
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
const updatedUser = await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
const updatedUser = await db.user.update({
where: {
stripeSubscriptionId: subscription.id,
},
data: {
stripeSubscriptionId: null,
stripePriceId: null,
stripeCurrentPeriodEnd: null,
plan: 'FREE',
},
});
await scoreUserLifecycle(updatedUser.id, 'subscription_deleted');
break;
}
plan: 'FREE',
},
});
await scoreUserLifecycle(updatedUser.id, 'subscription_deleted');
break;
}
}
return NextResponse.json({ received: true });