finished sub

This commit is contained in:
songtianlun 2025-08-06 23:14:19 +08:00
parent 5b36748048
commit 64e2ad63b8
5 changed files with 592 additions and 14 deletions

View File

@ -0,0 +1,103 @@
#!/usr/bin/env tsx
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function createTestSubscription() {
try {
console.log('🧪 Creating test subscription...')
// 使用真实的数据
const subscriptionData = {
userId: '9975b2c5-7955-48cf-9594-74b8d9beab25',
subscriptionPlanId: 'pro',
stripeSubscriptionId: 'sub_1Rt8nRLW0cChKPJ0Osn5UBcV',
stripeCustomerId: 'cus_SojwymqWZ4EXlZ',
isActive: true,
status: 'active',
startDate: new Date(1754492307 * 1000), // 2025-08-06T14:58:27.000Z
endDate: new Date(1757170707 * 1000), // 2025-09-06T14:58:27.000Z
metadata: {
test: true,
created_by: 'manual_script'
}
}
console.log('📊 Creating subscription with data:', subscriptionData)
// 检查是否已存在
const existing = await prisma.subscription.findFirst({
where: {
stripeSubscriptionId: subscriptionData.stripeSubscriptionId
}
})
if (existing) {
console.log('⚠️ Subscription already exists:', existing.id)
console.log('🗑️ Deleting existing subscription...')
await prisma.subscription.delete({
where: { id: existing.id }
})
}
// 创建新订阅
const newSubscription = await prisma.subscription.create({
data: subscriptionData
})
console.log('✅ Created subscription:', newSubscription.id)
// 更新用户的订阅计划
await prisma.user.update({
where: { id: subscriptionData.userId },
data: {
subscriptionPlanId: 'pro',
subscribePlan: 'pro'
}
})
console.log('✅ Updated user subscription plan')
// 验证创建结果
const createdSubscription = await prisma.subscription.findUnique({
where: { id: newSubscription.id },
include: {
user: {
select: {
email: true,
subscriptionPlanId: true,
subscribePlan: true
}
},
subscriptionPlan: {
select: {
name: true,
price: true
}
}
}
})
console.log('📋 Subscription details:')
console.log(' ID:', createdSubscription?.id)
console.log(' User:', createdSubscription?.user.email)
console.log(' Plan:', createdSubscription?.subscriptionPlan.name)
console.log(' Price:', `$${createdSubscription?.subscriptionPlan.price}`)
console.log(' Status:', createdSubscription?.status)
console.log(' Active:', createdSubscription?.isActive)
console.log(' Start:', createdSubscription?.startDate)
console.log(' End:', createdSubscription?.endDate)
console.log(' User Plan ID:', createdSubscription?.user.subscriptionPlanId)
console.log(' User Plan:', createdSubscription?.user.subscribePlan)
console.log('🎉 Test subscription created successfully!')
} catch (error) {
console.error('❌ Error creating test subscription:', error)
} finally {
await prisma.$disconnect()
}
}
createTestSubscription()

View File

@ -0,0 +1,88 @@
#!/usr/bin/env tsx
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function testSubscriptionTable() {
try {
console.log('🔍 Testing subscription table...')
// 测试查询所有订阅
const subscriptions = await prisma.subscription.findMany({
include: {
user: {
select: {
id: true,
email: true,
stripeCustomerId: true
}
},
subscriptionPlan: {
select: {
id: true,
name: true,
price: true
}
}
}
})
console.log(`📊 Found ${subscriptions.length} subscriptions:`)
subscriptions.forEach((sub, index) => {
console.log(`${index + 1}. Subscription ${sub.id}:`)
console.log(` - User: ${sub.user.email} (${sub.user.id})`)
console.log(` - Plan: ${sub.subscriptionPlan.name} ($${sub.subscriptionPlan.price})`)
console.log(` - Status: ${sub.status}`)
console.log(` - Active: ${sub.isActive}`)
console.log(` - Stripe ID: ${sub.stripeSubscriptionId}`)
console.log(` - Start: ${sub.startDate}`)
console.log(` - End: ${sub.endDate}`)
console.log('')
})
// 测试查询用户
const users = await prisma.user.findMany({
where: {
stripeCustomerId: {
not: null
}
},
select: {
id: true,
email: true,
stripeCustomerId: true,
subscriptionPlanId: true,
subscribePlan: true
}
})
console.log(`👥 Found ${users.length} users with Stripe customer IDs:`)
users.forEach((user, index) => {
console.log(`${index + 1}. ${user.email}:`)
console.log(` - User ID: ${user.id}`)
console.log(` - Stripe Customer ID: ${user.stripeCustomerId}`)
console.log(` - Subscription Plan ID: ${user.subscriptionPlanId}`)
console.log(` - Subscribe Plan: ${user.subscribePlan}`)
console.log('')
})
// 测试查询订阅套餐
const plans = await prisma.subscriptionPlan.findMany()
console.log(`📦 Found ${plans.length} subscription plans:`)
plans.forEach((plan, index) => {
console.log(`${index + 1}. ${plan.name} (${plan.id}):`)
console.log(` - Price: $${plan.price}`)
console.log(` - Stripe Price ID: ${plan.stripePriceId}`)
console.log(` - Active: ${plan.isActive}`)
console.log('')
})
} catch (error) {
console.error('❌ Error testing subscription table:', error)
} finally {
await prisma.$disconnect()
}
}
testSubscriptionTable()

View File

@ -0,0 +1,232 @@
#!/usr/bin/env tsx
// 模拟从你的日志中提取的 Stripe 订阅数据
const mockSubscriptionData = {
"id": "sub_1Rt8nRLW0cChKPJ0Osn5UBcV",
"object": "subscription",
"application": null,
"application_fee_percent": null,
"automatic_tax": {
"disabled_reason": null,
"enabled": false,
"liability": null
},
"billing_cycle_anchor": 1754492307,
"billing_cycle_anchor_config": null,
"billing_mode": {
"type": "classic"
},
"billing_thresholds": null,
"cancel_at": null,
"cancel_at_period_end": false,
"canceled_at": null,
"cancellation_details": {
"comment": null,
"feedback": null,
"reason": null
},
"collection_method": "charge_automatically",
"created": 1754492307,
"currency": "usd",
"customer": "cus_SojwymqWZ4EXlZ",
"days_until_due": null,
"default_payment_method": "pm_1Rt8nPLW0cChKPJ0EF0QrEyS",
"default_source": null,
"default_tax_rates": [],
"description": null,
"discounts": [],
"ended_at": null,
"invoice_settings": {
"account_tax_ids": null,
"issuer": {
"type": "self"
}
},
"items": {
"object": "list",
"data": [
{
"id": "si_SomMVRu4Bpje2r",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1754492308,
"current_period_end": 1757170707,
"current_period_start": 1754492307,
"discounts": [],
"metadata": {},
"plan": {
"id": "price_1RslfmLW0cChKPJ0VurJSg9I",
"object": "plan",
"active": true,
"amount": 1999,
"amount_decimal": "1999",
"billing_scheme": "per_unit",
"created": 1754403422,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {},
"meter": null,
"nickname": null,
"product": "prod_SoOSFPRNsYcTF8",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"price": {
"id": "price_1RslfmLW0cChKPJ0VurJSg9I",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1754403422,
"currency": "usd",
"custom_unit_amount": null,
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"product": "prod_SoOSFPRNsYcTF8",
"recurring": {
"interval": "month",
"interval_count": 1,
"meter": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"tax_behavior": "inclusive",
"tiers_mode": null,
"transform_quantity": null,
"type": "recurring",
"unit_amount": 1999,
"unit_amount_decimal": "1999"
},
"quantity": 1,
"subscription": "sub_1Rt8nRLW0cChKPJ0Osn5UBcV",
"tax_rates": []
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/subscription_items?subscription=sub_1Rt8nRLW0cChKPJ0Osn5UBcV"
},
"latest_invoice": "in_1Rt8nPLW0cChKPJ0YOG0OCof",
"livemode": false,
"metadata": {},
"next_pending_invoice_item_invoice": null,
"on_behalf_of": null,
"pause_collection": null,
"payment_settings": {
"payment_method_options": {
"acss_debit": null,
"bancontact": null,
"card": {
"network": null,
"request_three_d_secure": "automatic"
},
"customer_balance": null,
"konbini": null,
"sepa_debit": null,
"us_bank_account": null
},
"payment_method_types": [
"card"
],
"save_default_payment_method": "off"
},
"pending_invoice_item_interval": null,
"pending_setup_intent": null,
"pending_update": null,
"plan": {
"id": "price_1RslfmLW0cChKPJ0VurJSg9I",
"object": "plan",
"active": true,
"amount": 1999,
"amount_decimal": "1999",
"billing_scheme": "per_unit",
"created": 1754403422,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {},
"meter": null,
"nickname": null,
"product": "prod_SoOSFPRNsYcTF8",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"schedule": null,
"start_date": 1754492307,
"status": "active",
"test_clock": null,
"transfer_data": null,
"trial_end": null,
"trial_settings": {
"end_behavior": {
"missing_payment_method": "create_invoice"
}
},
"trial_start": null
}
function testWebhookData() {
console.log('🔍 Testing webhook data extraction...')
const subscription = mockSubscriptionData as any
console.log('📊 Subscription object keys:', Object.keys(subscription))
// 测试当前的提取逻辑
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('🎯 Extracted data (current logic):')
console.log(' customerId:', customerId)
console.log(' status:', status)
console.log(' stripeSubscriptionId:', stripeSubscriptionId)
console.log(' priceId:', priceId)
console.log(' currentPeriodStart:', currentPeriodStart)
console.log(' currentPeriodEnd:', currentPeriodEnd)
// 检查日期是否有效
if (currentPeriodStart) {
const startDate = new Date(currentPeriodStart * 1000)
console.log(' startDate:', startDate.toISOString())
} else {
console.log(' ❌ currentPeriodStart is undefined/null')
}
if (currentPeriodEnd) {
const endDate = new Date(currentPeriodEnd * 1000)
console.log(' endDate:', endDate.toISOString())
} else {
console.log(' ❌ currentPeriodEnd is undefined/null')
}
// 检查替代的日期字段
console.log('\n🔍 Looking for alternative date fields:')
console.log(' start_date:', subscription.start_date)
console.log(' created:', subscription.created)
console.log(' billing_cycle_anchor:', subscription.billing_cycle_anchor)
// 检查 items 中的日期
if (subscription.items?.data?.[0]) {
const item = subscription.items.data[0]
console.log(' item.current_period_start:', item.current_period_start)
console.log(' item.current_period_end:', item.current_period_end)
}
console.log('\n✅ Analysis complete!')
}
testWebhookData()

111
scripts/test-webhook-fix.ts Normal file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env tsx
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// 模拟修复后的 webhook 处理逻辑
async function testWebhookFix() {
try {
console.log('🧪 Testing webhook fix...')
// 模拟 Stripe 订阅数据
const mockSubscription = {
id: "sub_1Rt8nRLW0cChKPJ0Osn5UBcV",
customer: "cus_SojwymqWZ4EXlZ",
status: "active",
start_date: 1754492307,
items: {
data: [{
price: { id: "price_1RslfmLW0cChKPJ0VurJSg9I" },
current_period_start: 1754492307,
current_period_end: 1757170707
}]
}
}
// 提取数据(使用修复后的逻辑)
const customerId = mockSubscription.customer
const status = mockSubscription.status
const stripeSubscriptionId = mockSubscription.id
const items = mockSubscription.items
const priceId = items?.data[0]?.price?.id
const currentPeriodStart = items?.data[0]?.current_period_start || mockSubscription.start_date
const currentPeriodEnd = items?.data[0]?.current_period_end || (mockSubscription.start_date + 30 * 24 * 60 * 60)
console.log('📊 Extracted data:')
console.log(' customerId:', customerId)
console.log(' status:', status)
console.log(' stripeSubscriptionId:', stripeSubscriptionId)
console.log(' priceId:', priceId)
console.log(' currentPeriodStart:', currentPeriodStart)
console.log(' currentPeriodEnd:', currentPeriodEnd)
// 验证日期
if (!currentPeriodStart || !currentPeriodEnd) {
console.error('❌ Missing period dates')
return
}
const startDate = new Date(currentPeriodStart * 1000)
const endDate = new Date(currentPeriodEnd * 1000)
console.log('📅 Converted dates:')
console.log(' startDate:', startDate.toISOString())
console.log(' endDate:', endDate.toISOString())
// 检查日期是否有效
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
console.error('❌ Invalid dates')
return
}
console.log('✅ Dates are valid!')
// 查找用户
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.email)
// 查找套餐
const plan = await prisma.subscriptionPlan.findFirst({
where: { stripePriceId: priceId }
})
if (!plan) {
console.error('❌ Plan not found for price:', priceId)
return
}
console.log('📦 Found plan:', plan.name)
// 模拟创建订阅记录
console.log('🔄 Would create subscription with data:')
console.log({
userId: user.id,
subscriptionPlanId: plan.id,
stripeSubscriptionId,
stripeCustomerId: customerId,
isActive: true,
status: 'active',
startDate,
endDate,
})
console.log('✅ Webhook fix test completed successfully!')
} catch (error) {
console.error('❌ Error testing webhook fix:', error)
} finally {
await prisma.$disconnect()
}
}
testWebhookFix()

View File

@ -98,20 +98,47 @@ async function handleSubscriptionCreated(subscription: Record<string, unknown>)
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 items = subscription.items as { data: Array<{
price: { id: string }
current_period_start: number
current_period_end: number
}> }
const priceId = items?.data[0]?.price?.id
const currentPeriodStart = subscription.current_period_start as number
const currentPeriodEnd = subscription.current_period_end as number
// 从 subscription items 中获取周期日期,如果不存在则使用 subscription 的 start_date
const currentPeriodStart = items?.data[0]?.current_period_start || subscription.start_date as number
const currentPeriodEnd = items?.data[0]?.current_period_end || (subscription.start_date as number + 30 * 24 * 60 * 60) // 默认30天后
console.log(`📊 New subscription details:`, {
customerId,
status,
stripeSubscriptionId,
priceId
priceId,
currentPeriodStart,
currentPeriodEnd
})
// 验证必要的字段
if (!customerId || !stripeSubscriptionId || !priceId) {
console.error('❌ Missing required subscription data:', {
customerId: !!customerId,
stripeSubscriptionId: !!stripeSubscriptionId,
priceId: !!priceId
})
return
}
// 验证日期字段
if (!currentPeriodStart || !currentPeriodEnd) {
console.error('❌ Missing or invalid period dates:', {
currentPeriodStart,
currentPeriodEnd
})
return
}
// 查找用户
const user = await prisma.user.findFirst({
const user = await (prisma as any).user.findFirst({
where: { stripeCustomerId: customerId }
})
@ -126,13 +153,24 @@ async function handleSubscriptionCreated(subscription: Record<string, unknown>)
// 根据 Stripe 价格 ID 获取套餐 ID
const planId = await SubscriptionService.getPlanIdByStripePriceId(priceId)
// 创建日期对象,确保有效性
const startDate = new Date(currentPeriodStart * 1000)
const endDate = new Date(currentPeriodEnd * 1000)
console.log(`📅 Subscription dates:`, {
currentPeriodStart,
currentPeriodEnd,
startDate: startDate.toISOString(),
endDate: endDate.toISOString()
})
// 创建新的订阅记录
const newSubscription = await SubscriptionService.createRenewalSubscription(
user.id,
planId,
stripeSubscriptionId,
new Date(currentPeriodStart * 1000),
new Date(currentPeriodEnd * 1000),
startDate,
endDate,
customerId,
subscription
)
@ -154,10 +192,16 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
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 items = subscription.items as { data: Array<{
price: { id: string }
current_period_start: number
current_period_end: number
}> }
const priceId = items?.data[0]?.price?.id
const currentPeriodStart = subscription.current_period_start as number
const currentPeriodEnd = subscription.current_period_end as number
// 从 subscription items 中获取周期日期
const currentPeriodStart = items?.data[0]?.current_period_start || subscription.start_date as number
const currentPeriodEnd = items?.data[0]?.current_period_end || (subscription.start_date as number + 30 * 24 * 60 * 60)
console.log(`📊 Subscription details:`, {
customerId,
@ -167,7 +211,7 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
})
// 查找用户
const user = await prisma.user.findFirst({
const user = await (prisma as any).user.findFirst({
where: { stripeCustomerId: customerId }
})
@ -183,7 +227,7 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
const planId = await SubscriptionService.getPlanIdByStripePriceId(priceId)
// 查找现有的订阅记录(通过 Stripe 订阅 ID
const existingSubscription = await prisma.subscription.findFirst({
const existingSubscription = await (prisma as any).subscription.findFirst({
where: {
stripeSubscriptionId,
}
@ -191,7 +235,7 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
if (existingSubscription) {
// 更新现有的订阅记录
await prisma.subscription.update({
await (prisma as any).subscription.update({
where: { id: existingSubscription.id },
data: {
isActive: true,
@ -227,7 +271,7 @@ async function handleSubscriptionDeleted(subscription: Record<string, unknown>)
const customerId = subscription.customer as string
// 查找用户
const user = await prisma.user.findFirst({
const user = await (prisma as any).user.findFirst({
where: { stripeCustomerId: customerId }
})