fix subscribe
This commit is contained in:
parent
8282039b1d
commit
79ddfc6898
@ -122,6 +122,8 @@
|
||||
"monthlyAllowance": "Monthly Allowance",
|
||||
"purchase": "Purchase",
|
||||
"usage": "Usage",
|
||||
"subscriptionPayment": "Subscription Payment",
|
||||
"subscriptionRefund": "Subscription Refund",
|
||||
"simulation": "Simulation",
|
||||
"apiCall": "API Call",
|
||||
"export": "Export",
|
||||
@ -169,7 +171,6 @@
|
||||
"descending": "Descending",
|
||||
"itemsPerPage": "Items per page",
|
||||
"page": "Page",
|
||||
"of": "of",
|
||||
"total": "total",
|
||||
"editPrompt": "Edit Prompt",
|
||||
"deletePrompt": "Delete Prompt",
|
||||
|
@ -122,6 +122,8 @@
|
||||
"monthlyAllowance": "月度额度",
|
||||
"purchase": "购买充值",
|
||||
"usage": "消费使用",
|
||||
"subscriptionPayment": "订阅付费",
|
||||
"subscriptionRefund": "订阅退款",
|
||||
"simulation": "模拟器",
|
||||
"apiCall": "API调用",
|
||||
"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 { headers } from 'next/headers'
|
||||
import { SubscriptionService } from '@/lib/subscription-service'
|
||||
import { addCreditForSubscription, recordSubscriptionPayment, addCreditForSubscriptionRefund } from '@/lib/services/credit'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -279,6 +280,7 @@ async function handleSubscriptionUpdate(subscription: Record<string, unknown>) {
|
||||
async function handleSubscriptionDeleted(subscription: Record<string, unknown>) {
|
||||
try {
|
||||
const customerId = subscription.customer as string
|
||||
const stripeSubscriptionId = subscription.id as 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
|
||||
}
|
||||
|
||||
// 查找对应的数据库订阅记录
|
||||
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')
|
||||
|
||||
@ -309,11 +344,12 @@ async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
|
||||
|
||||
const subscriptionId = invoice.subscription 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:', {
|
||||
subscriptionId,
|
||||
customerId,
|
||||
amount: invoice.amount_paid,
|
||||
amount: amountPaid,
|
||||
status: invoice.status
|
||||
})
|
||||
|
||||
@ -322,11 +358,70 @@ async function handlePaymentSucceeded(invoice: Record<string, unknown>) {
|
||||
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...')
|
||||
const subscription = await stripe.subscriptions.retrieve(subscriptionId)
|
||||
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>)
|
||||
|
||||
|
@ -141,6 +141,10 @@ export default function CreditsPage() {
|
||||
return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200'
|
||||
case 'consumption':
|
||||
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:
|
||||
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')
|
||||
case 'consumption':
|
||||
return t('usage')
|
||||
case 'subscription_payment':
|
||||
return t('subscriptionPayment')
|
||||
case 'subscription_refund':
|
||||
return t('subscriptionRefund')
|
||||
default:
|
||||
return type
|
||||
}
|
||||
@ -277,6 +285,8 @@ export default function CreditsPage() {
|
||||
<option value="subscription_monthly">{t('monthlyAllowance')}</option>
|
||||
<option value="user_purchase">{t('purchase')}</option>
|
||||
<option value="consumption">{t('usage')}</option>
|
||||
<option value="subscription_payment">{t('subscriptionPayment')}</option>
|
||||
<option value="subscription_refund">{t('subscriptionRefund')}</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
|
@ -20,7 +20,7 @@ export interface CreditTransaction {
|
||||
export interface CreateCreditTransactionOptions {
|
||||
userId: string
|
||||
amount: number
|
||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'consumption'
|
||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'consumption' | 'subscription_payment' | 'subscription_refund'
|
||||
category?: string
|
||||
note?: string
|
||||
referenceId?: string
|
||||
@ -85,7 +85,7 @@ export async function createCreditTransaction(
|
||||
export async function addCredit(
|
||||
userId: string,
|
||||
amount: number,
|
||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase',
|
||||
type: 'system_gift' | 'subscription_monthly' | 'user_purchase' | 'subscription_refund',
|
||||
note?: string,
|
||||
expiresAt?: Date
|
||||
): Promise<CreditTransaction> {
|
||||
@ -298,3 +298,66 @@ export async function consumeCreditForSimulation(
|
||||
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