Files
soul-yongping/next-project/app/api/referral/bind/route.ts
2026-02-09 14:43:35 +08:00

207 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 推荐码绑定API - 使用 Prisma ORM
*
* 核心规则:
* 1. 链接带ID谁发的链接进的人就绑谁
* 2. 一级、一月:只有一级分销;绑定有效期一个月
* 3. 长期不发:别人发得多,客户会被「抢走」
* 4. 每天发:持续发的人绑定一直有效,收益越来越高
* 5. 约90%给分发:谁发得多谁拿得多
*/
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { getPrismaConfig } from '@/lib/prisma-helpers'
const DEFAULT_BINDING_DAYS = 30
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { userId, referralCode, openId, source } = body
const effectiveUserId = userId || (openId ? `user_${openId.slice(-8)}` : null)
if (!effectiveUserId || !referralCode) {
return NextResponse.json({
success: false,
error: '用户ID和推荐码不能为空'
}, { status: 400 })
}
// 获取绑定天数配置
let bindingDays = DEFAULT_BINDING_DAYS
try {
const config = await getPrismaConfig('referral_config')
if (config?.bindingDays) {
bindingDays = Number(config.bindingDays)
}
} catch (e) {
console.warn('[Referral Bind] 使用默认配置', DEFAULT_BINDING_DAYS)
}
// 查找推荐人(使用 Prisma
const referrer = await prisma.users.findUnique({
where: { referral_code: referralCode },
select: {
id: true,
nickname: true,
referral_code: true
}
})
if (!referrer) {
return NextResponse.json({
success: false,
error: '推荐码无效'
}, { status: 400 })
}
// 不能自己推荐自己
if (referrer.id === effectiveUserId) {
return NextResponse.json({
success: false,
error: '不能使用自己的推荐码'
}, { status: 400 })
}
// 检查用户是否存在
const user = await prisma.users.findFirst({
where: {
OR: [
{ id: effectiveUserId },
{ open_id: openId || effectiveUserId }
]
}
})
if (!user) {
return NextResponse.json({
success: false,
error: '用户不存在'
}, { status: 400 })
}
// 检查现有绑定关系
const existingBinding = await prisma.referral_bindings.findFirst({
where: {
referee_id: user.id,
status: 'active'
},
orderBy: { binding_date: 'desc' }
})
let action = 'new'
let oldReferrerId = null
// 计算新的过期时间
const expiryDate = new Date()
expiryDate.setDate(expiryDate.getDate() + bindingDays)
if (existingBinding) {
if (existingBinding.referrer_id === referrer.id) {
// 同一个推荐人 - 续期
action = 'renew'
await prisma.referral_bindings.update({
where: { id: existingBinding.id },
data: {
expiry_date: expiryDate,
binding_date: new Date()
}
})
console.log(`[Referral Bind] 续期: ${user.id} -> ${referrer.id}`)
} else {
// 不同推荐人 - 立即切换
action = 'switch'
oldReferrerId = existingBinding.referrer_id
// 使用 Prisma 事务确保原子性
await prisma.$transaction([
// 将旧绑定标记为 cancelled
prisma.referral_bindings.update({
where: { id: existingBinding.id },
data: { status: 'cancelled' }
}),
// 创建新绑定
prisma.referral_bindings.create({
data: {
id: `bind_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
referrer_id: referrer.id,
referee_id: user.id,
referral_code: referralCode,
status: 'active',
expiry_date: expiryDate,
binding_date: new Date()
}
})
])
console.log(`[Referral Bind] 立即切换: ${user.id}: ${oldReferrerId} -> ${referrer.id}`)
}
} else {
// 新绑定
await prisma.referral_bindings.create({
data: {
id: `bind_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
referrer_id: referrer.id,
referee_id: user.id,
referral_code: referralCode,
status: 'active',
expiry_date: expiryDate,
binding_date: new Date()
}
})
// 更新推荐人的推广数量
await prisma.users.update({
where: { id: referrer.id },
data: {
referral_count: { increment: 1 }
}
})
console.log(`[Referral Bind] 新绑定: ${user.id} -> ${referrer.id}`)
}
// 记录访问(如果有 referral_visits 表)
if (source) {
try {
await prisma.referral_visits.create({
data: {
referrer_id: referrer.id,
visitor_id: user.id,
visitor_openid: openId || null,
source: source || 'miniprogram',
page: null
}
})
} catch (e) {
console.log('[Referral Bind] 记录访问失败(表可能不存在)')
}
}
return NextResponse.json({
success: true,
message: action === 'renew' ? '绑定已续期' : action === 'switch' ? '推荐人已切换' : '绑定成功',
data: {
action,
referrer: {
id: referrer.id,
nickname: referrer.nickname
},
expiryDate,
bindingDays,
oldReferrerId
}
})
} catch (error) {
console.error('[Referral Bind] 错误:', error)
return NextResponse.json({
success: false,
error: '绑定失败: ' + (error as Error).message
}, { status: 500 })
}
}