fix subscribe
This commit is contained in:
parent
8282039b1d
commit
79ddfc6898
@ -122,6 +122,8 @@
|
|||||||
"monthlyAllowance": "Monthly Allowance",
|
"monthlyAllowance": "Monthly Allowance",
|
||||||
"purchase": "Purchase",
|
"purchase": "Purchase",
|
||||||
"usage": "Usage",
|
"usage": "Usage",
|
||||||
|
"subscriptionPayment": "Subscription Payment",
|
||||||
|
"subscriptionRefund": "Subscription Refund",
|
||||||
"simulation": "Simulation",
|
"simulation": "Simulation",
|
||||||
"apiCall": "API Call",
|
"apiCall": "API Call",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
@ -169,7 +171,6 @@
|
|||||||
"descending": "Descending",
|
"descending": "Descending",
|
||||||
"itemsPerPage": "Items per page",
|
"itemsPerPage": "Items per page",
|
||||||
"page": "Page",
|
"page": "Page",
|
||||||
"of": "of",
|
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"editPrompt": "Edit Prompt",
|
"editPrompt": "Edit Prompt",
|
||||||
"deletePrompt": "Delete Prompt",
|
"deletePrompt": "Delete Prompt",
|
||||||
|
@ -122,6 +122,8 @@
|
|||||||
"monthlyAllowance": "月度额度",
|
"monthlyAllowance": "月度额度",
|
||||||
"purchase": "购买充值",
|
"purchase": "购买充值",
|
||||||
"usage": "消费使用",
|
"usage": "消费使用",
|
||||||
|
"subscriptionPayment": "订阅付费",
|
||||||
|
"subscriptionRefund": "订阅退款",
|
||||||
"simulation": "模拟器",
|
"simulation": "模拟器",
|
||||||
"apiCall": "API调用",
|
"apiCall": "API调用",
|
||||||
"export": "导出功能",
|
"export": "导出功能",
|
||||||
|
105
scripts/test-subscription-credits.ts
Normal file
105
scripts/test-subscription-credits.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { prisma } from '../src/lib/prisma'
|
||||||
|
import { addCreditForSubscription, getUserBalance, getCreditStats } from '../src/lib/services/credit'
|
||||||
|
|
||||||
|
async function testSubscriptionCredits() {
|
||||||
|
try {
|
||||||
|
console.log('🧪 Testing subscription credit integration...')
|
||||||
|
|
||||||
|
// 找到一个测试用户
|
||||||
|
const user = await prisma.user.findFirst()
|
||||||
|
if (!user) {
|
||||||
|
console.log('❌ No user found. Please create a user first.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`👤 Testing with user: ${user.email} (ID: ${user.id})`)
|
||||||
|
|
||||||
|
// 获取用户当前余额
|
||||||
|
const initialBalance = await getUserBalance(user.id)
|
||||||
|
console.log(`💰 Initial balance: $${initialBalance.toFixed(2)}`)
|
||||||
|
|
||||||
|
// 查找Pro套餐
|
||||||
|
const proPlan = await prisma.subscriptionPlan.findFirst({
|
||||||
|
where: { name: 'pro', isActive: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!proPlan) {
|
||||||
|
console.log('❌ Pro plan not found. Please run the seed script first.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📋 Using plan: ${proPlan.displayName}`)
|
||||||
|
|
||||||
|
// 创建一个模拟订阅记录(通常这是由Stripe webhook创建的)
|
||||||
|
const subscription = await prisma.subscription.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
subscriptionPlanId: proPlan.id,
|
||||||
|
stripeSubscriptionId: `sub_test_${Date.now()}`,
|
||||||
|
stripeCustomerId: user.stripeCustomerId || `cus_test_${Date.now()}`,
|
||||||
|
isActive: true,
|
||||||
|
startDate: new Date(),
|
||||||
|
endDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30天后
|
||||||
|
status: 'active'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`✅ Created test subscription: ${subscription.id}`)
|
||||||
|
|
||||||
|
// 获取套餐的月度信用额度
|
||||||
|
const planLimits = proPlan.limits as { creditMonthly?: number }
|
||||||
|
const monthlyCreditAmount = planLimits?.creditMonthly || 0
|
||||||
|
|
||||||
|
if (monthlyCreditAmount > 0) {
|
||||||
|
console.log(`💳 Adding monthly credit allowance: $${monthlyCreditAmount}`)
|
||||||
|
|
||||||
|
// 使用我们的新function添加订阅信用
|
||||||
|
const creditTransaction = await addCreditForSubscription(
|
||||||
|
user.id,
|
||||||
|
monthlyCreditAmount,
|
||||||
|
subscription.id,
|
||||||
|
proPlan.displayName,
|
||||||
|
`${proPlan.displayName} monthly credit allowance - ${new Date().toISOString().slice(0, 7)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`✅ Created credit transaction: ${creditTransaction.id}`)
|
||||||
|
console.log(` Amount: $${creditTransaction.amount.toFixed(2)}`)
|
||||||
|
console.log(` Balance after: $${creditTransaction.balance.toFixed(2)}`)
|
||||||
|
console.log(` Type: ${creditTransaction.type}`)
|
||||||
|
console.log(` Category: ${creditTransaction.category}`)
|
||||||
|
console.log(` Reference: ${creditTransaction.referenceType}:${creditTransaction.referenceId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取更新后的余额和统计
|
||||||
|
const finalBalance = await getUserBalance(user.id)
|
||||||
|
const stats = await getCreditStats(user.id)
|
||||||
|
|
||||||
|
console.log(`\n📊 Final Results:`)
|
||||||
|
console.log(` Current Balance: $${finalBalance.toFixed(2)}`)
|
||||||
|
console.log(` Balance Increase: $${(finalBalance - initialBalance).toFixed(2)}`)
|
||||||
|
console.log(` Total Earned: $${stats.totalEarned.toFixed(2)}`)
|
||||||
|
console.log(` This Month Earned: $${stats.thisMonthEarned.toFixed(2)}`)
|
||||||
|
|
||||||
|
// 显示最新的credit交易记录
|
||||||
|
const recentTransactions = await prisma.credit.findMany({
|
||||||
|
where: { userId: user.id },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: 3
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`\n📝 Recent Credit Transactions:`)
|
||||||
|
recentTransactions.forEach(tx => {
|
||||||
|
console.log(` ${tx.createdAt.toISOString().slice(0, 19)} | ${tx.type.padEnd(20)} | ${tx.amount >= 0 ? '+' : ''}$${tx.amount.toFixed(2).padStart(8)} | Balance: $${tx.balance.toFixed(2)}`)
|
||||||
|
if (tx.note) console.log(` Note: ${tx.note}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('\n✅ Subscription credit integration test completed successfully!')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error testing subscription credits:', error)
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testSubscriptionCredits()
|
@ -3,6 +3,7 @@ import { stripe, STRIPE_CONFIG } from '@/lib/stripe'
|
|||||||
import { prisma } from '@/lib/prisma'
|
import { prisma } from '@/lib/prisma'
|
||||||
import { headers } from 'next/headers'
|
import { headers } from 'next/headers'
|
||||||
import { SubscriptionService } from '@/lib/subscription-service'
|
import { SubscriptionService } from '@/lib/subscription-service'
|
||||||
|
import { addCreditForSubscription, recordSubscriptionPayment, addCreditForSubscriptionRefund } from '@/lib/services/credit'
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@ -279,6 +280,7 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
|
|||||||
async function handleSubscriptionDeleted(subscription: Record<string, unknown>) {
|
async function handleSubscriptionDeleted(subscription: Record<string, unknown>) {
|
||||||
try {
|
try {
|
||||||
const customerId = subscription.customer as string
|
const customerId = subscription.customer as string
|
||||||
|
const stripeSubscriptionId = subscription.id as string
|
||||||
|
|
||||||
// 查找用户
|
// 查找用户
|
||||||
const users = await prisma.$queryRaw<Array<{id: string, email: string, stripeCustomerId: string}>>`
|
const users = await prisma.$queryRaw<Array<{id: string, email: string, stripeCustomerId: string}>>`
|
||||||
@ -294,6 +296,39 @@ async function handleSubscriptionDeleted(subscription: Record<string, unknown>)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找对应的数据库订阅记录
|
||||||
|
const dbSubscription = await prisma.subscription.findFirst({
|
||||||
|
where: { stripeSubscriptionId: stripeSubscriptionId },
|
||||||
|
include: { subscriptionPlan: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dbSubscription) {
|
||||||
|
// 标记数据库中的订阅为已取消
|
||||||
|
await prisma.subscription.update({
|
||||||
|
where: { id: dbSubscription.id },
|
||||||
|
data: {
|
||||||
|
isActive: false,
|
||||||
|
status: 'canceled',
|
||||||
|
updatedAt: new Date()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`✅ Marked subscription ${dbSubscription.id} as canceled in database`)
|
||||||
|
|
||||||
|
// TODO: 如果需要记录退款,可以在这里添加
|
||||||
|
// 这取决于具体的退款政策和Stripe配置
|
||||||
|
// const refundAmount = calculateRefundAmount(dbSubscription)
|
||||||
|
// if (refundAmount > 0) {
|
||||||
|
// await addCreditForSubscriptionRefund(
|
||||||
|
// user.id,
|
||||||
|
// refundAmount,
|
||||||
|
// dbSubscription.id,
|
||||||
|
// dbSubscription.subscriptionPlan.displayName,
|
||||||
|
// `Refund for canceled ${dbSubscription.subscriptionPlan.displayName} subscription`
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
// 将用户降级为免费计划
|
// 将用户降级为免费计划
|
||||||
await SubscriptionService.updateUserSubscriptionPlan(user.id, 'free')
|
await SubscriptionService.updateUserSubscriptionPlan(user.id, 'free')
|
||||||
|
|
||||||
@ -309,11 +344,12 @@ async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
|
|||||||
|
|
||||||
const subscriptionId = invoice.subscription as string
|
const subscriptionId = invoice.subscription as string
|
||||||
const customerId = invoice.customer as string
|
const customerId = invoice.customer as string
|
||||||
|
const amountPaid = (invoice.amount_paid as number) / 100 // Stripe amounts are in cents
|
||||||
|
|
||||||
console.log('Payment details:', {
|
console.log('Payment details:', {
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
customerId,
|
customerId,
|
||||||
amount: invoice.amount_paid,
|
amount: amountPaid,
|
||||||
status: invoice.status
|
status: invoice.status
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -322,11 +358,70 @@ async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找用户
|
||||||
|
const users = await prisma.$queryRaw<Array<{id: string, email: string, stripeCustomerId: string}>>`
|
||||||
|
SELECT id, email, "stripeCustomerId"
|
||||||
|
FROM users
|
||||||
|
WHERE "stripeCustomerId" = ${customerId}
|
||||||
|
LIMIT 1
|
||||||
|
`
|
||||||
|
const user = users[0] || null
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.error('❌ User not found for customer:', customerId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 获取订阅详情
|
// 获取订阅详情
|
||||||
console.log('📞 Retrieving subscription from Stripe...')
|
console.log('📞 Retrieving subscription from Stripe...')
|
||||||
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
|
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
|
||||||
console.log('✅ Retrieved subscription:', subscription.id, 'status:', subscription.status)
|
console.log('✅ Retrieved subscription:', subscription.id, 'status:', subscription.status)
|
||||||
|
|
||||||
|
// 获取套餐信息
|
||||||
|
const priceId = subscription.items.data[0]?.price?.id
|
||||||
|
if (priceId) {
|
||||||
|
const plan = await prisma.subscriptionPlan.findFirst({
|
||||||
|
where: { stripePriceId: priceId, isActive: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (plan) {
|
||||||
|
// 查找对应的数据库订阅记录
|
||||||
|
const dbSubscription = await prisma.subscription.findFirst({
|
||||||
|
where: { stripeSubscriptionId: subscriptionId }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dbSubscription) {
|
||||||
|
console.log('💳 Recording subscription payment and credit')
|
||||||
|
|
||||||
|
// 1. 记录订阅付费支出(可选,如果想跟踪用户支出)
|
||||||
|
// await recordSubscriptionPayment(
|
||||||
|
// user.id,
|
||||||
|
// amountPaid,
|
||||||
|
// dbSubscription.id,
|
||||||
|
// plan.displayName,
|
||||||
|
// `${plan.displayName} subscription payment - ${new Date().toISOString().slice(0, 7)}`
|
||||||
|
// )
|
||||||
|
|
||||||
|
// 2. 获取套餐配置中的月度信用额度
|
||||||
|
const planLimits = plan.limits as { creditMonthly?: number }
|
||||||
|
const monthlyCreditAmount = planLimits?.creditMonthly || 0
|
||||||
|
|
||||||
|
if (monthlyCreditAmount > 0) {
|
||||||
|
// 添加月度信用额度
|
||||||
|
await addCreditForSubscription(
|
||||||
|
user.id,
|
||||||
|
monthlyCreditAmount,
|
||||||
|
dbSubscription.id,
|
||||||
|
plan.displayName,
|
||||||
|
`${plan.displayName} monthly credit allowance - ${new Date().toISOString().slice(0, 7)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`✅ Added ${monthlyCreditAmount} USD monthly credit for user ${user.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理订阅更新(激活逻辑)
|
// 处理订阅更新(激活逻辑)
|
||||||
await handleSubscriptionUpdate(subscription as unknown as Record<string, unknown>)
|
await handleSubscriptionUpdate(subscription as unknown as Record<string, unknown>)
|
||||||
|
|
||||||
|
@ -141,6 +141,10 @@ export default function CreditsPage() {
|
|||||||
return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200'
|
return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200'
|
||||||
case 'consumption':
|
case 'consumption':
|
||||||
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||||||
|
case 'subscription_payment':
|
||||||
|
return 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200'
|
||||||
|
case 'subscription_refund':
|
||||||
|
return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'
|
||||||
default:
|
default:
|
||||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'
|
return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200'
|
||||||
}
|
}
|
||||||
@ -156,6 +160,10 @@ export default function CreditsPage() {
|
|||||||
return t('purchase')
|
return t('purchase')
|
||||||
case 'consumption':
|
case 'consumption':
|
||||||
return t('usage')
|
return t('usage')
|
||||||
|
case 'subscription_payment':
|
||||||
|
return t('subscriptionPayment')
|
||||||
|
case 'subscription_refund':
|
||||||
|
return t('subscriptionRefund')
|
||||||
default:
|
default:
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
@ -277,6 +285,8 @@ export default function CreditsPage() {
|
|||||||
<option value="subscription_monthly">{t('monthlyAllowance')}</option>
|
<option value="subscription_monthly">{t('monthlyAllowance')}</option>
|
||||||
<option value="user_purchase">{t('purchase')}</option>
|
<option value="user_purchase">{t('purchase')}</option>
|
||||||
<option value="consumption">{t('usage')}</option>
|
<option value="consumption">{t('usage')}</option>
|
||||||
|
<option value="subscription_payment">{t('subscriptionPayment')}</option>
|
||||||
|
<option value="subscription_refund">{t('subscriptionRefund')}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
|
@ -20,7 +20,7 @@ export interface CreditTransaction {
|
|||||||
export interface CreateCreditTransactionOptions {
|
export interface CreateCreditTransactionOptions {
|
||||||
userId: string
|
userId: string
|
||||||
amount: number
|
amount: number
|
||||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'consumption'
|
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'consumption' | 'subscription_payment' | 'subscription_refund'
|
||||||
category?: string
|
category?: string
|
||||||
note?: string
|
note?: string
|
||||||
referenceId?: string
|
referenceId?: string
|
||||||
@ -85,7 +85,7 @@ export async function createCreditTransaction(
|
|||||||
export async function addCredit(
|
export async function addCredit(
|
||||||
userId: string,
|
userId: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase',
|
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'subscription_refund',
|
||||||
note?: string,
|
note?: string,
|
||||||
expiresAt?: Date
|
expiresAt?: Date
|
||||||
): Promise<CreditTransaction> {
|
): Promise<CreditTransaction> {
|
||||||
@ -297,4 +297,67 @@ export async function consumeCreditForSimulation(
|
|||||||
'simulator_run',
|
'simulator_run',
|
||||||
note || `AI model simulation cost`
|
note || `AI model simulation cost`
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅付费时添加信用(用于记录订阅费用和赠送的信用)
|
||||||
|
*/
|
||||||
|
export async function addCreditForSubscription(
|
||||||
|
userId: string,
|
||||||
|
creditAmount: number,
|
||||||
|
subscriptionId: string,
|
||||||
|
planName: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<CreditTransaction> {
|
||||||
|
return createCreditTransaction({
|
||||||
|
userId,
|
||||||
|
amount: creditAmount,
|
||||||
|
type: 'subscription_monthly',
|
||||||
|
category: 'subscription',
|
||||||
|
referenceId: subscriptionId,
|
||||||
|
referenceType: 'subscription',
|
||||||
|
note: note || `${planName} subscription monthly credit`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录订阅费用支出(如果需要记录用户付费)
|
||||||
|
*/
|
||||||
|
export async function recordSubscriptionPayment(
|
||||||
|
userId: string,
|
||||||
|
paymentAmount: number,
|
||||||
|
subscriptionId: string,
|
||||||
|
planName: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<CreditTransaction> {
|
||||||
|
return createCreditTransaction({
|
||||||
|
userId,
|
||||||
|
amount: -paymentAmount, // 负数表示支出
|
||||||
|
type: 'subscription_payment',
|
||||||
|
category: 'subscription',
|
||||||
|
referenceId: subscriptionId,
|
||||||
|
referenceType: 'subscription',
|
||||||
|
note: note || `${planName} subscription payment`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅取消时记录退款(如果有的话)
|
||||||
|
*/
|
||||||
|
export async function addCreditForSubscriptionRefund(
|
||||||
|
userId: string,
|
||||||
|
refundAmount: number,
|
||||||
|
subscriptionId: string,
|
||||||
|
planName: string,
|
||||||
|
note?: string
|
||||||
|
): Promise<CreditTransaction> {
|
||||||
|
return createCreditTransaction({
|
||||||
|
userId,
|
||||||
|
amount: refundAmount,
|
||||||
|
type: 'subscription_refund',
|
||||||
|
category: 'subscription',
|
||||||
|
referenceId: subscriptionId,
|
||||||
|
referenceType: 'subscription',
|
||||||
|
note: note || `${planName} subscription refund`
|
||||||
|
})
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user