search console SEO ableitungen
This commit is contained in:
@@ -1,89 +1,89 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_CANCEL);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with subscription info
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
stripeSubscriptionId: true,
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Already on free plan
|
||||
if (user.plan === 'FREE') {
|
||||
return NextResponse.json({ error: 'Already on free plan' }, { status: 400 });
|
||||
}
|
||||
|
||||
// No active subscription
|
||||
if (!user.stripeSubscriptionId) {
|
||||
// Just update plan to FREE if somehow plan is not FREE but no subscription
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
plan: 'FREE',
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
||||
// Cancel the Stripe subscription
|
||||
await stripe.subscriptions.cancel(user.stripeSubscriptionId);
|
||||
|
||||
// Update user plan to FREE
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
plan: 'FREE',
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error canceling subscription:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to cancel subscription' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_CANCEL);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with subscription info
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
stripeSubscriptionId: true,
|
||||
plan: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Already on free plan
|
||||
if (user.plan === 'FREE') {
|
||||
return NextResponse.json({ error: 'Already on free plan' }, { status: 400 });
|
||||
}
|
||||
|
||||
// No active subscription
|
||||
if (!user.stripeSubscriptionId) {
|
||||
// Just update plan to FREE if somehow plan is not FREE but no subscription
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
plan: 'FREE',
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
||||
// Cancel the Stripe subscription
|
||||
await stripe.subscriptions.cancel(user.stripeSubscriptionId);
|
||||
|
||||
// Update user plan to FREE
|
||||
await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
plan: 'FREE',
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error canceling subscription:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to cancel subscription' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Get user email from request body (since we're using simple auth, not NextAuth)
|
||||
const { priceId, plan, userEmail } = await request.json();
|
||||
|
||||
if (!userEmail) {
|
||||
return NextResponse.json({ error: 'Unauthorized - No user email provided' }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!priceId || !plan) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing priceId or plan' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
const user = await db.user.findUnique({
|
||||
where: { email: userEmail },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Create or get Stripe customer
|
||||
let customerId = user.stripeCustomerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await stripe.customers.create({
|
||||
email: user.email,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
// Update user with Stripe customer ID
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { stripeCustomerId: customerId },
|
||||
});
|
||||
}
|
||||
|
||||
// Create Stripe Checkout Session
|
||||
const checkoutSession = await stripe.checkout.sessions.create({
|
||||
customer: customerId,
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
plan,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: checkoutSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating checkout session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Get user email from request body (since we're using simple auth, not NextAuth)
|
||||
const { priceId, plan, userEmail } = await request.json();
|
||||
|
||||
if (!userEmail) {
|
||||
return NextResponse.json({ error: 'Unauthorized - No user email provided' }, { status: 401 });
|
||||
}
|
||||
|
||||
if (!priceId || !plan) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing priceId or plan' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
const user = await db.user.findUnique({
|
||||
where: { email: userEmail },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Create or get Stripe customer
|
||||
let customerId = user.stripeCustomerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await stripe.customers.create({
|
||||
email: user.email,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
// Update user with Stripe customer ID
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { stripeCustomerId: customerId },
|
||||
});
|
||||
}
|
||||
|
||||
// Create Stripe Checkout Session
|
||||
const checkoutSession = await stripe.checkout.sessions.create({
|
||||
customer: customerId,
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
plan,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: checkoutSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating checkout session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,115 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { stripe, STRIPE_PLANS } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { cookies } from 'next/headers';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Get user from cookie (using userId like other routes)
|
||||
const cookieStore = await cookies();
|
||||
const userId = cookieStore.get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_CHECKOUT);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized - Please log in' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get plan and billing interval from request
|
||||
const { plan, billingInterval = 'month' } = await request.json();
|
||||
|
||||
if (!plan || !['PRO', 'BUSINESS'].includes(plan)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid plan. Must be PRO or BUSINESS' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get the Stripe price ID for the plan
|
||||
const planConfig = STRIPE_PLANS[plan as 'PRO' | 'BUSINESS'];
|
||||
const priceId = billingInterval === 'year' ? planConfig.priceIdYearly : planConfig.priceId;
|
||||
|
||||
if (!priceId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Stripe price ID not configured for this plan' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Create or get Stripe customer
|
||||
let customerId = user.stripeCustomerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await stripe.customers.create({
|
||||
email: user.email,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
// Update user with Stripe customer ID
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { stripeCustomerId: customerId },
|
||||
});
|
||||
}
|
||||
|
||||
// Create Stripe Checkout Session
|
||||
const checkoutSession = await stripe.checkout.sessions.create({
|
||||
customer: customerId,
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
plan,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: checkoutSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating checkout session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { stripe, STRIPE_PLANS } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { cookies } from 'next/headers';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Get user from cookie (using userId like other routes)
|
||||
const cookieStore = await cookies();
|
||||
const userId = cookieStore.get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_CHECKOUT);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized - Please log in' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get plan and billing interval from request
|
||||
const { plan, billingInterval = 'month' } = await request.json();
|
||||
|
||||
if (!plan || !['PRO', 'BUSINESS'].includes(plan)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid plan. Must be PRO or BUSINESS' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get the Stripe price ID for the plan
|
||||
const planConfig = STRIPE_PLANS[plan as 'PRO' | 'BUSINESS'];
|
||||
const priceId = billingInterval === 'year' ? planConfig.priceIdYearly : planConfig.priceId;
|
||||
|
||||
if (!priceId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Stripe price ID not configured for this plan' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get user from database
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Create or get Stripe customer
|
||||
let customerId = user.stripeCustomerId;
|
||||
|
||||
if (!customerId) {
|
||||
const customer = await stripe.customers.create({
|
||||
email: user.email,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
customerId = customer.id;
|
||||
|
||||
// Update user with Stripe customer ID
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: { stripeCustomerId: customerId },
|
||||
});
|
||||
}
|
||||
|
||||
// Create Stripe Checkout Session
|
||||
const checkoutSession = await stripe.checkout.sessions.create({
|
||||
customer: customerId,
|
||||
mode: 'subscription',
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/pricing?canceled=true`,
|
||||
metadata: {
|
||||
userId: user.id,
|
||||
plan,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: checkoutSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating checkout session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_PORTAL);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with Stripe customer ID
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
stripeCustomerId: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// If user doesn't have a Stripe customer ID, they can't access the portal
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No active subscription found' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Create Stripe Customer Portal session
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/settings`,
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: portalSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating portal session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create portal session' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
// Rate Limiting (user-based)
|
||||
const clientId = userId || getClientIdentifier(request);
|
||||
const rateLimitResult = rateLimit(clientId, RateLimits.STRIPE_PORTAL);
|
||||
|
||||
if (!rateLimitResult.success) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000)
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': rateLimitResult.limit.toString(),
|
||||
'X-RateLimit-Remaining': rateLimitResult.remaining.toString(),
|
||||
'X-RateLimit-Reset': rateLimitResult.reset.toString(),
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get user with Stripe customer ID
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
stripeCustomerId: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// If user doesn't have a Stripe customer ID, they can't access the portal
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No active subscription found' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Create Stripe Customer Portal session
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: user.stripeCustomerId,
|
||||
return_url: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/settings`,
|
||||
});
|
||||
|
||||
return NextResponse.json({ url: portalSession.url });
|
||||
} catch (error) {
|
||||
console.error('Error creating portal session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create portal session' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,113 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
/**
|
||||
* Manual sync endpoint to update user subscription from Stripe
|
||||
* Use this if the automatic webhook/verify failed
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Use cookie-based auth
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json({ error: 'No Stripe customer ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all subscriptions for this customer
|
||||
const subscriptions = await stripe.subscriptions.list({
|
||||
customer: user.stripeCustomerId,
|
||||
status: 'active',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (subscriptions.data.length === 0) {
|
||||
// No active subscription - set to FREE
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan: 'FREE',
|
||||
message: 'No active subscription found, set to FREE plan',
|
||||
});
|
||||
}
|
||||
|
||||
const subscription: any = subscriptions.data[0];
|
||||
|
||||
// Determine plan from price ID
|
||||
const priceId = subscription.items.data[0]?.price?.id;
|
||||
let plan = 'PRO'; // default
|
||||
|
||||
// Check against known price IDs
|
||||
if (priceId === process.env.STRIPE_PRICE_ID_BUSINESS_MONTHLY ||
|
||||
priceId === process.env.STRIPE_PRICE_ID_BUSINESS_YEARLY) {
|
||||
plan = 'BUSINESS';
|
||||
} else if (priceId === process.env.STRIPE_PRICE_ID_PRO_MONTHLY ||
|
||||
priceId === process.env.STRIPE_PRICE_ID_PRO_YEARLY) {
|
||||
plan = 'PRO';
|
||||
}
|
||||
|
||||
// Get current_period_end
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
console.log('Syncing subscription:', {
|
||||
subscriptionId: subscription.id,
|
||||
priceId,
|
||||
plan,
|
||||
periodEndTimestamp,
|
||||
currentPeriodEnd,
|
||||
});
|
||||
|
||||
// Update user in database
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
stripePriceId: priceId,
|
||||
stripeCurrentPeriodEnd: currentPeriodEnd,
|
||||
plan: plan as any,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan,
|
||||
subscriptionId: subscription.id,
|
||||
currentPeriodEnd,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error syncing subscription:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
/**
|
||||
* Manual sync endpoint to update user subscription from Stripe
|
||||
* Use this if the automatic webhook/verify failed
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Use cookie-based auth
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json({ error: 'No Stripe customer ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get all subscriptions for this customer
|
||||
const subscriptions = await stripe.subscriptions.list({
|
||||
customer: user.stripeCustomerId,
|
||||
status: 'active',
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (subscriptions.data.length === 0) {
|
||||
// No active subscription - set to FREE
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan: 'FREE',
|
||||
message: 'No active subscription found, set to FREE plan',
|
||||
});
|
||||
}
|
||||
|
||||
const subscription: any = subscriptions.data[0];
|
||||
|
||||
// Determine plan from price ID
|
||||
const priceId = subscription.items.data[0]?.price?.id;
|
||||
let plan = 'PRO'; // default
|
||||
|
||||
// Check against known price IDs
|
||||
if (priceId === process.env.STRIPE_PRICE_ID_BUSINESS_MONTHLY ||
|
||||
priceId === process.env.STRIPE_PRICE_ID_BUSINESS_YEARLY) {
|
||||
plan = 'BUSINESS';
|
||||
} else if (priceId === process.env.STRIPE_PRICE_ID_PRO_MONTHLY ||
|
||||
priceId === process.env.STRIPE_PRICE_ID_PRO_YEARLY) {
|
||||
plan = 'PRO';
|
||||
}
|
||||
|
||||
// Get current_period_end
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
console.log('Syncing subscription:', {
|
||||
subscriptionId: subscription.id,
|
||||
priceId,
|
||||
plan,
|
||||
periodEndTimestamp,
|
||||
currentPeriodEnd,
|
||||
});
|
||||
|
||||
// Update user in database
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
stripePriceId: priceId,
|
||||
stripeCurrentPeriodEnd: currentPeriodEnd,
|
||||
plan: plan as any,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan,
|
||||
subscriptionId: subscription.id,
|
||||
currentPeriodEnd,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error syncing subscription:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Use cookie-based auth instead of NextAuth
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json({ error: 'No Stripe customer ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get the most recent checkout session for this customer
|
||||
const checkoutSessions = await stripe.checkout.sessions.list({
|
||||
customer: user.stripeCustomerId,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (checkoutSessions.data.length === 0) {
|
||||
return NextResponse.json({ error: 'No checkout session found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const checkoutSession = checkoutSessions.data[0];
|
||||
|
||||
// Only process if payment was successful
|
||||
if (checkoutSession.payment_status === 'paid' && checkoutSession.subscription) {
|
||||
const subscriptionId = typeof checkoutSession.subscription === 'string'
|
||||
? checkoutSession.subscription
|
||||
: checkoutSession.subscription.id;
|
||||
|
||||
// Retrieve the full subscription object
|
||||
const subscription: any = await stripe.subscriptions.retrieve(subscriptionId);
|
||||
|
||||
// Determine plan from metadata or price ID
|
||||
const plan = checkoutSession.metadata?.plan || 'PRO';
|
||||
|
||||
// Debug log to see the subscription structure
|
||||
console.log('Full subscription object:', JSON.stringify(subscription, null, 2));
|
||||
|
||||
// Get current_period_end - Stripe returns it as a Unix timestamp
|
||||
// Try different possible field names
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // Default to 30 days from now
|
||||
|
||||
console.log('Subscription data:', {
|
||||
id: subscription.id,
|
||||
periodEndTimestamp,
|
||||
currentPeriodEnd,
|
||||
priceId: subscription.items?.data?.[0]?.price?.id,
|
||||
});
|
||||
|
||||
// Update user in database
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
stripePriceId: subscription.items.data[0].price.id,
|
||||
stripeCurrentPeriodEnd: currentPeriodEnd,
|
||||
plan: plan as any,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan,
|
||||
subscriptionId: subscription.id,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Payment not completed' }, { status: 400 });
|
||||
} catch (error) {
|
||||
console.error('Error verifying session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Use cookie-based auth instead of NextAuth
|
||||
const userId = cookies().get('userId')?.value;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (!user.stripeCustomerId) {
|
||||
return NextResponse.json({ error: 'No Stripe customer ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get the most recent checkout session for this customer
|
||||
const checkoutSessions = await stripe.checkout.sessions.list({
|
||||
customer: user.stripeCustomerId,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (checkoutSessions.data.length === 0) {
|
||||
return NextResponse.json({ error: 'No checkout session found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const checkoutSession = checkoutSessions.data[0];
|
||||
|
||||
// Only process if payment was successful
|
||||
if (checkoutSession.payment_status === 'paid' && checkoutSession.subscription) {
|
||||
const subscriptionId = typeof checkoutSession.subscription === 'string'
|
||||
? checkoutSession.subscription
|
||||
: checkoutSession.subscription.id;
|
||||
|
||||
// Retrieve the full subscription object
|
||||
const subscription: any = await stripe.subscriptions.retrieve(subscriptionId);
|
||||
|
||||
// Determine plan from metadata or price ID
|
||||
const plan = checkoutSession.metadata?.plan || 'PRO';
|
||||
|
||||
// Debug log to see the subscription structure
|
||||
console.log('Full subscription object:', JSON.stringify(subscription, null, 2));
|
||||
|
||||
// Get current_period_end - Stripe returns it as a Unix timestamp
|
||||
// Try different possible field names
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // Default to 30 days from now
|
||||
|
||||
console.log('Subscription data:', {
|
||||
id: subscription.id,
|
||||
periodEndTimestamp,
|
||||
currentPeriodEnd,
|
||||
priceId: subscription.items?.data?.[0]?.price?.id,
|
||||
});
|
||||
|
||||
// Update user in database
|
||||
await db.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
stripePriceId: subscription.items.data[0].price.id,
|
||||
stripeCurrentPeriodEnd: currentPeriodEnd,
|
||||
plan: plan as any,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
plan,
|
||||
subscriptionId: subscription.id,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Payment not completed' }, { status: 400 });
|
||||
} catch (error) {
|
||||
console.error('Error verifying session:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +1,116 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { headers } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.text();
|
||||
const signature = headers().get('stripe-signature');
|
||||
|
||||
if (!signature) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No signature' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
body,
|
||||
signature,
|
||||
process.env.STRIPE_WEBHOOK_SECRET!
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Webhook signature verification failed:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid signature' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed': {
|
||||
const session = event.data.object as Stripe.Checkout.Session;
|
||||
|
||||
if (session.mode === 'subscription') {
|
||||
const subscription: any = await stripe.subscriptions.retrieve(
|
||||
session.subscription as string
|
||||
);
|
||||
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'customer.subscription.updated': {
|
||||
const subscription: any = event.data.object as Stripe.Subscription;
|
||||
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? 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,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'customer.subscription.deleted': {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
await db.user.update({
|
||||
where: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
},
|
||||
data: {
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ received: true });
|
||||
} catch (error) {
|
||||
console.error('Error processing webhook:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Webhook processing failed' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { headers } from 'next/headers';
|
||||
import { stripe } from '@/lib/stripe';
|
||||
import { db } from '@/lib/db';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.text();
|
||||
const signature = headers().get('stripe-signature');
|
||||
|
||||
if (!signature) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No signature' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
body,
|
||||
signature,
|
||||
process.env.STRIPE_WEBHOOK_SECRET!
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Webhook signature verification failed:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid signature' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed': {
|
||||
const session = event.data.object as Stripe.Checkout.Session;
|
||||
|
||||
if (session.mode === 'subscription') {
|
||||
const subscription: any = await stripe.subscriptions.retrieve(
|
||||
session.subscription as string
|
||||
);
|
||||
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? new Date(periodEndTimestamp * 1000)
|
||||
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'customer.subscription.updated': {
|
||||
const subscription: any = event.data.object as Stripe.Subscription;
|
||||
|
||||
const periodEndTimestamp = subscription.current_period_end
|
||||
|| subscription.currentPeriodEnd
|
||||
|| subscription.billing_cycle_anchor;
|
||||
|
||||
const currentPeriodEnd = periodEndTimestamp
|
||||
? 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,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'customer.subscription.deleted': {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
await db.user.update({
|
||||
where: {
|
||||
stripeSubscriptionId: subscription.id,
|
||||
},
|
||||
data: {
|
||||
stripeSubscriptionId: null,
|
||||
stripePriceId: null,
|
||||
stripeCurrentPeriodEnd: null,
|
||||
plan: 'FREE',
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ received: true });
|
||||
} catch (error) {
|
||||
console.error('Error processing webhook:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Webhook processing failed' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user