Files
soul-yongping/app/api/referral/data/route.ts

307 lines
11 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 - 增强版
*
* 可见数据:
* - 绑定用户数(当前有效绑定)
* - 通过链接进的人数(总访问量)
* - 带来的付款人数(已转化购买)
* - 收益统计90%归分发者)
*/
import { NextRequest, NextResponse } from 'next/server'
import { query, getConfig } from '@/lib/db'
// 分成比例默认90%给推广者)
const DISTRIBUTOR_SHARE = 0.9
/**
* GET - 获取分销数据
*/
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
if (!userId) {
return NextResponse.json({
success: false,
error: '用户ID不能为空'
}, { status: 400 })
}
try {
// 获取分销配置
let distributorShare = DISTRIBUTOR_SHARE
let minWithdrawAmount = 10 // 默认最低提现金额
try {
const config = await getConfig('referral_config')
if (config?.distributorShare) {
distributorShare = config.distributorShare / 100
}
if (config?.minWithdrawAmount) {
minWithdrawAmount = Number(config.minWithdrawAmount)
}
} catch (e) { /* 使用默认配置 */ }
// 1. 获取用户基本信息
const users = await query(`
SELECT id, nickname, referral_code, earnings, pending_earnings,
withdrawn_earnings, referral_count
FROM users WHERE id = ?
`, [userId]) as any[]
if (users.length === 0) {
return NextResponse.json({
success: false,
error: '用户不存在'
}, { status: 404 })
}
const user = users[0]
// 2. 获取绑定关系统计(新逻辑:基于 purchase_count
let bindingStats = { total: 0, active: 0, converted: 0, expired: 0 }
try {
const bindings = await query(`
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 'active' AND expiry_date > NOW() THEN 1 ELSE 0 END) as active,
SUM(CASE WHEN status = 'active' AND purchase_count > 0 THEN 1 ELSE 0 END) as converted,
SUM(CASE WHEN status IN ('expired', 'cancelled') OR (status = 'active' AND expiry_date <= NOW()) THEN 1 ELSE 0 END) as expired
FROM referral_bindings
WHERE referrer_id = ?
`, [userId]) as any[]
if (bindings.length > 0) {
bindingStats = {
total: parseInt(bindings[0].total) || 0,
active: parseInt(bindings[0].active) || 0,
converted: parseInt(bindings[0].converted) || 0,
expired: parseInt(bindings[0].expired) || 0
}
}
} catch (e) { /* 忽略 */ }
// 3. 获取通过链接进入的总人数(访问日志)
let totalVisits = 0
try {
const visits = await query(`
SELECT COUNT(DISTINCT visitor_id) as count
FROM referral_visits
WHERE referrer_id = ?
`, [userId]) as any[]
totalVisits = parseInt(visits[0]?.count) || 0
} catch (e) { /* 访问记录表可能不存在 */ }
// 如果没有访问记录表,用绑定总数替代
if (totalVisits === 0) {
totalVisits = bindingStats.total
}
// 4. 获取带来的付款人数和金额
let paymentStats = { paidCount: 0, totalAmount: 0 }
try {
const payments = await query(`
SELECT
COUNT(DISTINCT o.user_id) as paid_count,
COALESCE(SUM(o.amount), 0) as total_amount
FROM orders o
JOIN referral_bindings rb ON o.user_id = rb.referee_id
WHERE rb.referrer_id = ? AND o.status = 'paid'
`, [userId]) as any[]
if (payments.length > 0) {
paymentStats = {
paidCount: parseInt(payments[0].paid_count) || 0,
totalAmount: parseFloat(payments[0].total_amount) || 0
}
}
} catch (e) { /* 忽略 */ }
// 5. 获取活跃绑定用户列表
const activeBindings = await query(`
SELECT rb.id, rb.referee_id, rb.expiry_date, rb.binding_date,
u.nickname, u.avatar, u.has_full_book,
DATEDIFF(rb.expiry_date, NOW()) as days_remaining
FROM referral_bindings rb
JOIN users u ON rb.referee_id = u.id
WHERE rb.referrer_id = ? AND rb.status = 'active' AND rb.expiry_date > NOW()
ORDER BY rb.binding_date DESC
LIMIT 50
`, [userId]) as any[]
// 6. 获取已转化用户列表(新逻辑:有购买记录的活跃绑定)
const convertedBindings = await query(`
SELECT rb.id, rb.referee_id, rb.last_purchase_date as conversion_date,
rb.total_commission as commission_amount, rb.purchase_count,
u.nickname, u.avatar,
(SELECT COALESCE(SUM(amount), 0) FROM orders WHERE user_id = rb.referee_id AND status = 'paid') as order_amount
FROM referral_bindings rb
JOIN users u ON rb.referee_id = u.id
WHERE rb.referrer_id = ? AND rb.status = 'active' AND rb.purchase_count > 0
ORDER BY rb.last_purchase_date DESC
LIMIT 50
`, [userId]) as any[]
// 6.5 获取已过期用户列表
const expiredBindings = await query(`
SELECT rb.id, rb.referee_id, rb.expiry_date, rb.binding_date,
u.nickname, u.avatar
FROM referral_bindings rb
JOIN users u ON rb.referee_id = u.id
WHERE rb.referrer_id = ? AND (rb.status = 'expired' OR (rb.status = 'active' AND rb.expiry_date <= NOW()))
ORDER BY rb.expiry_date DESC
LIMIT 50
`, [userId]) as any[]
// 7. 获取待审核提现金额
let pendingWithdrawAmount = 0
try {
const pendingResult = await query(`
SELECT COALESCE(SUM(amount), 0) as pending_amount
FROM withdrawals
WHERE user_id = ? AND status = 'pending'
`, [userId]) as any[]
pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
} catch (e) {
console.log('[ReferralData] 获取待审核提现金额失败:', e)
}
// 8. 获取收益明细(包含买家信息和商品详情)
let earningsDetails: any[] = []
try {
earningsDetails = await query(`
SELECT
o.id,
o.order_sn,
o.amount,
o.product_type,
o.product_id,
o.description,
o.pay_time,
u.nickname as buyer_nickname,
u.avatar as buyer_avatar,
rb.total_commission / rb.purchase_count as commission_per_order
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN referral_bindings rb ON o.user_id = rb.referee_id AND rb.referrer_id = ?
WHERE o.status = 'paid' AND o.referrer_id = ?
ORDER BY o.pay_time DESC
LIMIT 30
`, [userId, userId]) as any[]
} catch (e) {
console.log('[ReferralData] 获取收益明细失败:', e)
}
// 8. 计算预估收益
const estimatedEarnings = paymentStats.totalAmount * distributorShare
return NextResponse.json({
success: true,
data: {
// === 核心可见数据 ===
// 绑定用户数(当前有效绑定)
bindingCount: bindingStats.active,
// 通过链接进的人数
visitCount: totalVisits,
// 带来的付款人数
paidCount: paymentStats.paidCount,
// 已过期用户数
expiredCount: bindingStats.expired,
// === 收益数据 ===
// 累计佣金总额(所有获得的佣金)
totalCommission: Math.round((
(parseFloat(user.earnings) || 0) +
(parseFloat(user.pending_earnings) || 0) +
(parseFloat(user.withdrawn_earnings) || 0)
) * 100) / 100,
// 可提现金额pending_earnings
availableEarnings: parseFloat(user.pending_earnings) || 0,
// 待审核金额(提现申请中的金额)
pendingWithdrawAmount: Math.round(pendingWithdrawAmount * 100) / 100,
// 已提现金额
withdrawnEarnings: parseFloat(user.withdrawn_earnings) || 0,
// 已结算收益(保留兼容)
earnings: parseFloat(user.earnings) || 0,
// 待结算收益(保留兼容)
pendingEarnings: parseFloat(user.pending_earnings) || 0,
// 预估总收益
estimatedEarnings: Math.round(estimatedEarnings * 100) / 100,
// 分成比例
shareRate: Math.round(distributorShare * 100),
// 最低提现金额(新增:给小程序使用)
minWithdrawAmount,
// === 推荐码 ===
referralCode: user.referral_code,
referralCount: user.referral_count || bindingStats.total,
// === 详细统计 ===
stats: {
totalBindings: bindingStats.total,
activeBindings: bindingStats.active,
convertedBindings: bindingStats.converted,
expiredBindings: bindingStats.expired,
// 即将过期7天内
expiringCount: activeBindings.filter((b: any) => b.days_remaining <= 7 && b.days_remaining > 0).length,
// 总支付金额
totalPaymentAmount: paymentStats.totalAmount
},
// === 用户列表 ===
activeUsers: activeBindings.map((b: any) => ({
id: b.referee_id,
nickname: b.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.avatar,
daysRemaining: Math.max(0, b.days_remaining),
hasFullBook: b.has_full_book,
bindingDate: b.binding_date,
status: 'active'
})),
convertedUsers: convertedBindings.map((b: any) => ({
id: b.referee_id,
nickname: b.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.avatar,
commission: parseFloat(b.commission_amount) || 0,
orderAmount: parseFloat(b.order_amount) || 0,
purchaseCount: parseInt(b.purchase_count) || 0,
conversionDate: b.conversion_date,
status: 'converted'
})),
// 已过期用户列表
expiredUsers: expiredBindings.map((b: any) => ({
id: b.referee_id,
nickname: b.nickname || '用户' + b.referee_id.slice(-4),
avatar: b.avatar,
bindingDate: b.binding_date,
expiryDate: b.expiry_date,
status: 'expired'
})),
// === 收益明细 ===
earningsDetails: earningsDetails.map((e: any) => ({
id: e.id,
orderSn: e.order_sn,
amount: parseFloat(e.amount),
commission: parseFloat(e.commission_per_order) || parseFloat(e.amount) * distributorShare,
productType: e.product_type,
productId: e.product_id,
description: e.description,
buyerNickname: e.buyer_nickname || '用户' + e.id?.toString().slice(-4),
buyerAvatar: e.buyer_avatar,
payTime: e.pay_time
}))
}
})
} catch (error) {
console.error('[ReferralData] 错误:', error)
return NextResponse.json({
success: false,
error: '获取分销数据失败: ' + (error as Error).message
}, { status: 500 })
}
}