257 lines
8.3 KiB
TypeScript
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)
|
|
}
|
|
}
|