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

257 lines
8.3 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { stripe, STRIPE_CONFIG } from '@/lib/stripe'
import { prisma } from '@/lib/prisma'
import { headers } from 'next/headers'
import { SubscriptionService } from '@/lib/subscription-service'
export async function POST(request: NextRequest) {
try {
console.log('🔔 Webhook received')
const body = await request.text()
const headersList = await headers()
const signature = headersList.get('stripe-signature')
if (!signature) {
console.log('❌ No signature found')
return NextResponse.json({ error: 'No signature' }, { status: 400 })
}
// 验证 webhook 签名
const event = stripe.webhooks.constructEvent(
body,
signature,
STRIPE_CONFIG.webhookSecret
)
console.log(`📨 Processing event: ${event.type}`)
// 处理不同类型的事件
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutSessionCompleted(event.data.object as unknown as Record<string, unknown>)
break
case 'customer.subscription.created':
case 'customer.subscription.updated':
await handleSubscriptionUpdate(event.data.object as unknown as Record<string, unknown>)
break
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object as unknown as Record<string, unknown>)
break
case 'invoice.payment_succeeded':
await handlePaymentSucceeded(event.data.object as unknown as Record<string, unknown>)
break
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object as unknown as Record<string, unknown>)
break
default:
console.log(`Unhandled event type: ${event.type}`)
}
return NextResponse.json({ received: true })
} catch (error) {
console.error('Webhook error:', error)
return NextResponse.json(
{ error: 'Webhook handler failed' },
{ status: 400 }
)
}
}
async function handleCheckoutSessionCompleted(session: Record<string, unknown>) {
try {
console.log('🛒 Processing checkout session completed')
console.log('Session data:', JSON.stringify(session, null, 2))
const subscriptionId = session.subscription as string
console.log('Subscription ID from session:', subscriptionId)
if (!subscriptionId) {
console.log('❌ No subscription ID found in checkout session')
return
}
// 获取订阅详情
console.log('📞 Retrieving subscription from Stripe...')
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
console.log('✅ Retrieved subscription:', subscription.id, 'status:', subscription.status)
// 处理订阅更新
await handleSubscriptionUpdate(subscription as unknown as Record<string, unknown>)
} catch (error) {
console.error('❌ Error handling checkout session completed:', error)
}
}
async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
try {
console.log('🔄 Processing subscription update')
const customerId = subscription.customer as string
const status = subscription.status as string
const stripeSubscriptionId = subscription.id as string
const items = subscription.items as { data: Array<{ price: { id: string } }> }
const priceId = items?.data[0]?.price?.id
const currentPeriodStart = subscription.current_period_start as number
const currentPeriodEnd = subscription.current_period_end as number
console.log(`📊 Subscription details:`, {
customerId,
status,
stripeSubscriptionId,
priceId
})
// 查找用户
const user = await prisma.user.findFirst({
where: { stripeCustomerId: customerId }
})
if (!user) {
console.error('❌ User not found for customer:', customerId)
return
}
console.log(`👤 Found user: ${user.id}`)
if (status === 'active' || status === 'trialing') {
// 根据 Stripe 价格 ID 获取套餐 ID
const planId = await SubscriptionService.getPlanIdByStripePriceId(priceId)
// 检查是否已有待激活的订阅记录(通过用户 ID 查找)
const existingSubscription = await prisma.subscription.findFirst({
where: {
userId: user.id,
status: 'pending',
subscriptionPlanId: planId
},
orderBy: { createdAt: 'desc' }
})
if (existingSubscription) {
// 激活现有的订阅记录,并设置 Stripe 订阅 ID
await prisma.subscription.update({
where: { id: existingSubscription.id },
data: {
stripeSubscriptionId,
isActive: true,
status: 'active',
startDate: new Date(currentPeriodStart * 1000),
endDate: new Date(currentPeriodEnd * 1000),
metadata: subscription ? JSON.parse(JSON.stringify(subscription)) : undefined,
updatedAt: new Date()
}
})
console.log(`✅ Activated existing subscription ${existingSubscription.id} for user ${user.id}`)
} else {
// 检查是否是续订(已有活跃订阅)
const activeSubscription = await SubscriptionService.getUserActiveSubscription(user.id)
if (activeSubscription) {
// 这是续订,创建新的订阅记录
await SubscriptionService.createRenewalSubscription(
user.id,
planId,
stripeSubscriptionId,
new Date(currentPeriodStart * 1000),
new Date(currentPeriodEnd * 1000),
customerId,
subscription
)
console.log(`Created renewal subscription for user ${user.id}`)
} else {
// 直接创建并激活订阅(可能是通过其他方式创建的订阅)
await SubscriptionService.createRenewalSubscription(
user.id,
planId,
stripeSubscriptionId,
new Date(currentPeriodStart * 1000),
new Date(currentPeriodEnd * 1000),
customerId,
subscription
)
console.log(`Created new subscription for user ${user.id}`)
}
}
}
// 更新用户的默认订阅套餐(保持向后兼容)
const planId = status === 'active' || status === 'trialing'
? await SubscriptionService.getPlanIdByStripePriceId(priceId)
: 'free'
await SubscriptionService.updateUserSubscriptionPlan(user.id, planId)
} catch (error) {
console.error('Error handling subscription update:', error)
}
}
async function handleSubscriptionDeleted(subscription: Record<string, unknown>) {
try {
const customerId = subscription.customer as string
// 查找用户
const user = await prisma.user.findFirst({
where: { stripeCustomerId: customerId }
})
if (!user) {
console.error('User not found for customer:', customerId)
return
}
// 将用户降级为免费计划
await SubscriptionService.updateUserSubscriptionPlan(user.id, 'free')
console.log(`Reset user ${user.id} to free plan`)
} catch (error) {
console.error('Error handling subscription deletion:', error)
}
}
async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
try {
console.log('💰 Processing payment succeeded')
const subscriptionId = invoice.subscription as string
const customerId = invoice.customer as string
console.log('Payment details:', {
subscriptionId,
customerId,
amount: invoice.amount_paid,
status: invoice.status
})
if (!subscriptionId) {
console.log('❌ No subscription ID found in invoice, skipping')
return
}
// 获取订阅详情
console.log('📞 Retrieving subscription from Stripe...')
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
console.log('✅ Retrieved subscription:', subscription.id, 'status:', subscription.status)
// 处理订阅更新(激活逻辑)
await handleSubscriptionUpdate(subscription as unknown as Record<string, unknown>)
} catch (error) {
console.error('❌ Error handling payment success:', error)
}
}
async function handlePaymentFailed(_invoice: Record<string, unknown>) {
try {
// 这里可以添加额外的逻辑,比如发送提醒邮件等
} catch (error) {
console.error('Error handling payment failure:', error)
}
}