feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新: 1. 按H5网页端完全重构匹配功能(match页面) - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募 - 资源对接等类型弹出手机号/微信号输入框 - 去掉重新匹配按钮,改为返回按钮 2. 修复所有卡片对齐和宽度问题 - 目录页附录卡片居中 - 首页阅读进度卡片满宽度 - 我的页面菜单卡片对齐 - 推广中心分享卡片统一宽度 3. 修复目录页图标和文字对齐 - section-icon固定40rpx宽高 - section-title与图标垂直居中 4. 更新真实完整文章标题(62篇) - 从book目录读取真实markdown文件名 - 替换之前的简化标题 5. 新增文章数据API - /api/db/chapters - 获取完整书籍结构 - 支持按ID获取单篇文章内容
This commit is contained in:
@@ -2,13 +2,38 @@ import { type NextRequest, NextResponse } from "next/server"
|
||||
import crypto from "crypto"
|
||||
|
||||
// 存客宝API配置
|
||||
const CKB_API_KEY = "fyngh-ecy9h-qkdae-epwd5-rz6kd"
|
||||
const CKB_API_KEY = process.env.CKB_API_KEY || "fyngh-ecy9h-qkdae-epwd5-rz6kd"
|
||||
const CKB_API_URL = "https://ckbapi.quwanzhi.com/v1/api/scenarios"
|
||||
|
||||
// 生成签名
|
||||
function generateSign(apiKey: string, timestamp: number): string {
|
||||
const signStr = `${apiKey}${timestamp}`
|
||||
return crypto.createHash("md5").update(signStr).digest("hex")
|
||||
// 生成签名 - 根据文档实现
|
||||
function generateSign(params: Record<string, any>, apiKey: string): string {
|
||||
// 1. 移除 sign、apiKey、portrait 字段
|
||||
const filteredParams = { ...params }
|
||||
delete filteredParams.sign
|
||||
delete filteredParams.apiKey
|
||||
delete filteredParams.portrait
|
||||
|
||||
// 2. 移除空值字段
|
||||
const nonEmptyParams: Record<string, any> = {}
|
||||
for (const [key, value] of Object.entries(filteredParams)) {
|
||||
if (value !== null && value !== "") {
|
||||
nonEmptyParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 按参数名升序排序
|
||||
const sortedKeys = Object.keys(nonEmptyParams).sort()
|
||||
|
||||
// 4. 拼接参数值
|
||||
const stringToSign = sortedKeys.map(key => nonEmptyParams[key]).join("")
|
||||
|
||||
// 5. 第一次 MD5
|
||||
const firstMd5 = crypto.createHash("md5").update(stringToSign).digest("hex")
|
||||
|
||||
// 6. 拼接 apiKey 再次 MD5
|
||||
const sign = crypto.createHash("md5").update(firstMd5 + apiKey).digest("hex")
|
||||
|
||||
return sign
|
||||
}
|
||||
|
||||
// 不同类型对应的source标签
|
||||
@@ -16,44 +41,69 @@ const sourceMap: Record<string, string> = {
|
||||
team: "团队招募",
|
||||
investor: "资源对接",
|
||||
mentor: "导师顾问",
|
||||
partner: "创业合伙",
|
||||
}
|
||||
|
||||
const tagsMap: Record<string, string> = {
|
||||
team: "切片团队,团队招募",
|
||||
investor: "资源对接,资源群",
|
||||
mentor: "导师顾问,咨询服务",
|
||||
partner: "创业合伙,创业伙伴",
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { type, phone, name, wechatId, remark } = body
|
||||
const { type, phone, wechat, name, userId, remark } = body
|
||||
|
||||
// 验证必填参数
|
||||
if (!type || !phone) {
|
||||
return NextResponse.json({ success: false, message: "缺少必填参数" }, { status: 400 })
|
||||
// 验证必填参数 - 手机号或微信号至少一个
|
||||
if (!phone && !wechat) {
|
||||
return NextResponse.json({ success: false, message: "请提供手机号或微信号" }, { status: 400 })
|
||||
}
|
||||
|
||||
// 验证类型
|
||||
if (!["team", "investor", "mentor"].includes(type)) {
|
||||
if (!["team", "investor", "mentor", "partner"].includes(type)) {
|
||||
return NextResponse.json({ success: false, message: "无效的加入类型" }, { status: 400 })
|
||||
}
|
||||
|
||||
// 生成时间戳和签名
|
||||
// 生成时间戳(秒级)
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
const sign = generateSign(CKB_API_KEY, timestamp)
|
||||
|
||||
// 构建请求参数
|
||||
// 构建请求参数(不包含sign)
|
||||
const requestParams: Record<string, any> = {
|
||||
timestamp,
|
||||
source: `创业实验-${sourceMap[type]}`,
|
||||
tags: tagsMap[type],
|
||||
siteTags: "创业实验APP",
|
||||
remark: remark || `用户通过创业实验APP申请${sourceMap[type]}`,
|
||||
}
|
||||
|
||||
// 添加可选字段
|
||||
if (phone) requestParams.phone = phone
|
||||
if (wechat) requestParams.wechatId = wechat
|
||||
if (name) requestParams.name = name
|
||||
|
||||
// 生成签名
|
||||
const sign = generateSign(requestParams, CKB_API_KEY)
|
||||
|
||||
// 构建最终请求体
|
||||
const requestBody = {
|
||||
...requestParams,
|
||||
apiKey: CKB_API_KEY,
|
||||
sign,
|
||||
timestamp,
|
||||
phone,
|
||||
name: name || "",
|
||||
wechatId: wechatId || "",
|
||||
source: sourceMap[type],
|
||||
remark: remark || `来自创业实验APP-${sourceMap[type]}`,
|
||||
tags: tagsMap[type],
|
||||
portrait: {
|
||||
type: 4, // 互动行为
|
||||
source: 0, // 本站
|
||||
sourceData: {
|
||||
joinType: type,
|
||||
joinLabel: sourceMap[type],
|
||||
userId: userId || "",
|
||||
device: "webapp",
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
remark: `${sourceMap[type]}申请`,
|
||||
uniqueId: `soul_${phone || wechat}_${timestamp}`,
|
||||
},
|
||||
}
|
||||
|
||||
// 调用存客宝API
|
||||
@@ -67,13 +117,14 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (response.ok && result.code === 0) {
|
||||
if (result.code === 200) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `成功加入${sourceMap[type]}`,
|
||||
message: result.message === "已存在" ? "您已加入,我们会尽快联系您" : `成功加入${sourceMap[type]}`,
|
||||
data: result.data,
|
||||
})
|
||||
} else {
|
||||
console.error("CKB API Error:", result)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: result.message || "加入失败,请稍后重试",
|
||||
|
||||
131
app/api/ckb/match/route.ts
Normal file
131
app/api/ckb/match/route.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import crypto from "crypto"
|
||||
|
||||
// 存客宝API配置
|
||||
const CKB_API_KEY = process.env.CKB_API_KEY || "fyngh-ecy9h-qkdae-epwd5-rz6kd"
|
||||
const CKB_API_URL = "https://ckbapi.quwanzhi.com/v1/api/scenarios"
|
||||
|
||||
// 生成签名 - 根据文档实现
|
||||
function generateSign(params: Record<string, any>, apiKey: string): string {
|
||||
// 1. 移除 sign、apiKey、portrait 字段
|
||||
const filteredParams = { ...params }
|
||||
delete filteredParams.sign
|
||||
delete filteredParams.apiKey
|
||||
delete filteredParams.portrait
|
||||
|
||||
// 2. 移除空值字段
|
||||
const nonEmptyParams: Record<string, any> = {}
|
||||
for (const [key, value] of Object.entries(filteredParams)) {
|
||||
if (value !== null && value !== "") {
|
||||
nonEmptyParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 按参数名升序排序
|
||||
const sortedKeys = Object.keys(nonEmptyParams).sort()
|
||||
|
||||
// 4. 拼接参数值
|
||||
const stringToSign = sortedKeys.map(key => nonEmptyParams[key]).join("")
|
||||
|
||||
// 5. 第一次 MD5
|
||||
const firstMd5 = crypto.createHash("md5").update(stringToSign).digest("hex")
|
||||
|
||||
// 6. 拼接 apiKey 再次 MD5
|
||||
const sign = crypto.createHash("md5").update(firstMd5 + apiKey).digest("hex")
|
||||
|
||||
return sign
|
||||
}
|
||||
|
||||
// 匹配类型映射
|
||||
const matchTypeMap: Record<string, string> = {
|
||||
partner: "创业合伙",
|
||||
investor: "资源对接",
|
||||
mentor: "导师顾问",
|
||||
team: "团队招募",
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { matchType, phone, wechat, userId, nickname, matchedUser } = body
|
||||
|
||||
// 验证必填参数 - 手机号或微信号至少一个
|
||||
if (!phone && !wechat) {
|
||||
return NextResponse.json({ success: false, message: "请提供手机号或微信号" }, { status: 400 })
|
||||
}
|
||||
|
||||
// 生成时间戳(秒级)
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
|
||||
// 构建请求参数(不包含sign)
|
||||
const requestParams: Record<string, any> = {
|
||||
timestamp,
|
||||
source: `创业实验-找伙伴匹配`,
|
||||
tags: `找伙伴,${matchTypeMap[matchType] || "创业合伙"}`,
|
||||
siteTags: "创业实验APP,匹配用户",
|
||||
remark: `用户发起${matchTypeMap[matchType] || "创业合伙"}匹配`,
|
||||
}
|
||||
|
||||
// 添加联系方式
|
||||
if (phone) requestParams.phone = phone
|
||||
if (wechat) requestParams.wechatId = wechat
|
||||
if (nickname) requestParams.name = nickname
|
||||
|
||||
// 生成签名
|
||||
const sign = generateSign(requestParams, CKB_API_KEY)
|
||||
|
||||
// 构建最终请求体
|
||||
const requestBody = {
|
||||
...requestParams,
|
||||
apiKey: CKB_API_KEY,
|
||||
sign,
|
||||
portrait: {
|
||||
type: 4, // 互动行为
|
||||
source: 0, // 本站
|
||||
sourceData: {
|
||||
action: "match",
|
||||
matchType: matchType,
|
||||
matchLabel: matchTypeMap[matchType] || "创业合伙",
|
||||
userId: userId || "",
|
||||
matchedUserId: matchedUser?.id || "",
|
||||
matchedUserNickname: matchedUser?.nickname || "",
|
||||
matchScore: matchedUser?.matchScore || 0,
|
||||
device: "webapp",
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
remark: `找伙伴匹配-${matchTypeMap[matchType] || "创业合伙"}`,
|
||||
uniqueId: `soul_match_${phone || wechat}_${timestamp}`,
|
||||
},
|
||||
}
|
||||
|
||||
// 调用存客宝API
|
||||
const response = await fetch(CKB_API_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.code === 200) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "匹配记录已上报",
|
||||
data: result.data,
|
||||
})
|
||||
} else {
|
||||
console.error("CKB Match API Error:", result)
|
||||
// 即使上报失败也返回成功,不影响用户体验
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "匹配成功",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("存客宝匹配API调用失败:", error)
|
||||
// 即使出错也返回成功,不影响用户体验
|
||||
return NextResponse.json({ success: true, message: "匹配成功" })
|
||||
}
|
||||
}
|
||||
@@ -7,24 +7,33 @@ export async function GET() {
|
||||
enabled: true,
|
||||
qrCode: "/images/wechat-pay.png",
|
||||
account: "卡若",
|
||||
appId: process.env.TENCENT_APP_ID || "1251077262", // From .env or default
|
||||
// 敏感信息后端处理,不完全暴露给前端
|
||||
websiteAppId: "wx432c93e275548671",
|
||||
websiteAppSecret: "25b7e7fdb7998e5107e242ebb6ddabd0",
|
||||
serviceAppId: "wx7c0dbf34ddba300d",
|
||||
serviceAppSecret: "f865ef18c43dfea6cbe3b1f1aebdb82e",
|
||||
mpVerifyCode: "SP8AfZJyAvprRORT",
|
||||
merchantId: "1318592501",
|
||||
apiKey: "wx3e31b068be59ddc131b068be59ddc2",
|
||||
groupQrCode: "/images/party-group-qr.png",
|
||||
},
|
||||
alipay: {
|
||||
enabled: true,
|
||||
qrCode: "/images/alipay.png",
|
||||
account: "卡若",
|
||||
appId: process.env.ALIPAY_ACCESS_KEY_ID || "LTAI5t9zkiWmFtHG8qmtdysW", // Using Access Key as placeholder ID
|
||||
partnerId: "2088511801157159",
|
||||
securityKey: "lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp",
|
||||
mobilePayEnabled: true,
|
||||
paymentInterface: "official_instant",
|
||||
},
|
||||
usdt: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
network: "TRC20",
|
||||
address: process.env.USDT_WALLET_ADDRESS || "TWeq9xxxxxxxxxxxxxxxxxxxx",
|
||||
address: "",
|
||||
exchangeRate: 7.2
|
||||
},
|
||||
paypal: {
|
||||
enabled: false,
|
||||
email: process.env.PAYPAL_CLIENT_ID || "",
|
||||
email: "",
|
||||
exchangeRate: 7.2
|
||||
}
|
||||
},
|
||||
@@ -41,9 +50,15 @@ export async function GET() {
|
||||
},
|
||||
authorInfo: {
|
||||
name: "卡若",
|
||||
description: "私域运营与技术公司主理人",
|
||||
description: "连续创业者,私域运营专家,每天早上6-9点在Soul派对房分享真实商业故事",
|
||||
liveTime: "06:00-09:00",
|
||||
platform: "Soul"
|
||||
platform: "Soul派对房"
|
||||
},
|
||||
siteConfig: {
|
||||
siteName: "一场soul的创业实验",
|
||||
siteTitle: "一场soul的创业实验",
|
||||
siteDescription: "来自Soul派对房的真实商业故事",
|
||||
primaryColor: "#00CED1"
|
||||
},
|
||||
system: {
|
||||
version: "1.0.0",
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function GET(request: NextRequest) {
|
||||
const content = fs.readFileSync(fullPath, "utf-8")
|
||||
return NextResponse.json({ content, isCustom: false })
|
||||
} catch (error) {
|
||||
console.error("[v0] Error reading file:", error)
|
||||
console.error("[Karuo] Error reading file:", error)
|
||||
return NextResponse.json({ error: "Failed to read file" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
194
app/api/db/book/route.ts
Normal file
194
app/api/db/book/route.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { bookDB } from '@/lib/db'
|
||||
import { bookData } from '@/lib/book-data'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
// 获取章节
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const id = searchParams.get('id')
|
||||
const action = searchParams.get('action')
|
||||
|
||||
// 导出所有章节
|
||||
if (action === 'export') {
|
||||
const data = await bookDB.exportAll()
|
||||
return new NextResponse(data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Disposition': 'attachment; filename=book_sections.json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 从文件系统读取章节内容
|
||||
if (action === 'read' && id) {
|
||||
// 查找章节文件路径
|
||||
let filePath = ''
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
const section = chapter.sections.find(s => s.id === id)
|
||||
if (section) {
|
||||
filePath = section.filePath
|
||||
break
|
||||
}
|
||||
}
|
||||
if (filePath) break
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '章节不存在'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
const fullPath = path.join(process.cwd(), filePath)
|
||||
const content = await fs.readFile(fullPath, 'utf-8')
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
section: { id, filePath, content }
|
||||
})
|
||||
}
|
||||
|
||||
if (id) {
|
||||
const section = await bookDB.getSection(id)
|
||||
return NextResponse.json({ success: true, section })
|
||||
}
|
||||
|
||||
const sections = await bookDB.getAllSections()
|
||||
return NextResponse.json({ success: true, sections })
|
||||
} catch (error: any) {
|
||||
console.error('Get book sections error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 创建或更新章节
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { action, data } = body
|
||||
|
||||
// 导入章节
|
||||
if (action === 'import') {
|
||||
const count = await bookDB.importSections(JSON.stringify(data))
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `成功导入 ${count} 个章节`
|
||||
})
|
||||
}
|
||||
|
||||
// 同步book-data到数据库
|
||||
if (action === 'sync') {
|
||||
let count = 0
|
||||
let sortOrder = 0
|
||||
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
for (const section of chapter.sections) {
|
||||
sortOrder++
|
||||
const existing = await bookDB.getSection(section.id)
|
||||
|
||||
// 读取文件内容
|
||||
let content = ''
|
||||
try {
|
||||
const fullPath = path.join(process.cwd(), section.filePath)
|
||||
content = await fs.readFile(fullPath, 'utf-8')
|
||||
} catch (e) {
|
||||
console.warn(`Cannot read file: ${section.filePath}`)
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
await bookDB.updateSection(section.id, {
|
||||
title: section.title,
|
||||
content,
|
||||
price: section.price,
|
||||
is_free: section.isFree
|
||||
})
|
||||
} else {
|
||||
await bookDB.createSection({
|
||||
id: section.id,
|
||||
part_id: part.id,
|
||||
chapter_id: chapter.id,
|
||||
title: section.title,
|
||||
content,
|
||||
price: section.price,
|
||||
is_free: section.isFree,
|
||||
sort_order: sortOrder
|
||||
})
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `成功同步 ${count} 个章节到数据库`
|
||||
})
|
||||
}
|
||||
|
||||
// 创建单个章节
|
||||
const section = await bookDB.createSection(data)
|
||||
return NextResponse.json({ success: true, section })
|
||||
} catch (error: any) {
|
||||
console.error('Create/Import book section error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 更新章节
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { id, ...updates } = body
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少章节ID'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
// 如果要保存到文件系统
|
||||
if (updates.content && updates.saveToFile) {
|
||||
// 查找章节文件路径
|
||||
let filePath = ''
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
const section = chapter.sections.find(s => s.id === id)
|
||||
if (section) {
|
||||
filePath = section.filePath
|
||||
break
|
||||
}
|
||||
}
|
||||
if (filePath) break
|
||||
}
|
||||
|
||||
if (filePath) {
|
||||
const fullPath = path.join(process.cwd(), filePath)
|
||||
await fs.writeFile(fullPath, updates.content, 'utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
await bookDB.updateSection(id, updates)
|
||||
const section = await bookDB.getSection(id)
|
||||
|
||||
return NextResponse.json({ success: true, section })
|
||||
} catch (error: any) {
|
||||
console.error('Update book section error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
272
app/api/db/chapters/route.ts
Normal file
272
app/api/db/chapters/route.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Soul创业实验 - 文章数据API
|
||||
* 用于存储和获取章节数据
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// 文章数据结构
|
||||
interface Section {
|
||||
id: string
|
||||
title: string
|
||||
isFree: boolean
|
||||
price: number
|
||||
content?: string
|
||||
filePath?: string
|
||||
}
|
||||
|
||||
interface Chapter {
|
||||
id: string
|
||||
title: string
|
||||
sections: Section[]
|
||||
}
|
||||
|
||||
interface Part {
|
||||
id: string
|
||||
number: string
|
||||
title: string
|
||||
subtitle: string
|
||||
chapters: Chapter[]
|
||||
}
|
||||
|
||||
// 书籍目录结构映射
|
||||
const BOOK_STRUCTURE = [
|
||||
{
|
||||
id: 'part-1',
|
||||
number: '一',
|
||||
title: '真实的人',
|
||||
subtitle: '人与人之间的底层逻辑',
|
||||
folder: '第一篇|真实的人',
|
||||
chapters: [
|
||||
{ id: 'chapter-1', title: '第1章|人与人之间的底层逻辑', folder: '第1章|人与人之间的底层逻辑' },
|
||||
{ id: 'chapter-2', title: '第2章|人性困境案例', folder: '第2章|人性困境案例' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-2',
|
||||
number: '二',
|
||||
title: '真实的行业',
|
||||
subtitle: '电商、内容、传统行业解析',
|
||||
folder: '第二篇|真实的行业',
|
||||
chapters: [
|
||||
{ id: 'chapter-3', title: '第3章|电商篇', folder: '第3章|电商篇' },
|
||||
{ id: 'chapter-4', title: '第4章|内容商业篇', folder: '第4章|内容商业篇' },
|
||||
{ id: 'chapter-5', title: '第5章|传统行业篇', folder: '第5章|传统行业篇' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-3',
|
||||
number: '三',
|
||||
title: '真实的错误',
|
||||
subtitle: '我和别人犯过的错',
|
||||
folder: '第三篇|真实的错误',
|
||||
chapters: [
|
||||
{ id: 'chapter-6', title: '第6章|我人生错过的4件大钱', folder: '第6章|我人生错过的4件大钱' },
|
||||
{ id: 'chapter-7', title: '第7章|别人犯的错误', folder: '第7章|别人犯的错误' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-4',
|
||||
number: '四',
|
||||
title: '真实的赚钱',
|
||||
subtitle: '底层结构与真实案例',
|
||||
folder: '第四篇|真实的赚钱',
|
||||
chapters: [
|
||||
{ id: 'chapter-8', title: '第8章|底层结构', folder: '第8章|底层结构' },
|
||||
{ id: 'chapter-9', title: '第9章|我在Soul上亲访的赚钱案例', folder: '第9章|我在Soul上亲访的赚钱案例' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'part-5',
|
||||
number: '五',
|
||||
title: '真实的社会',
|
||||
subtitle: '未来职业与商业生态',
|
||||
folder: '第五篇|真实的社会',
|
||||
chapters: [
|
||||
{ id: 'chapter-10', title: '第10章|未来职业的变化趋势', folder: '第10章|未来职业的变化趋势' },
|
||||
{ id: 'chapter-11', title: '第11章|中国社会商业生态的未来', folder: '第11章|中国社会商业生态的未来' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 免费章节ID
|
||||
const FREE_SECTIONS = ['1.1', 'preface', 'epilogue', 'appendix-1', 'appendix-2', 'appendix-3']
|
||||
|
||||
// 从book目录读取真实文章数据
|
||||
function loadBookData(): Part[] {
|
||||
const bookPath = path.join(process.cwd(), 'book')
|
||||
const parts: Part[] = []
|
||||
|
||||
for (const partConfig of BOOK_STRUCTURE) {
|
||||
const part: Part = {
|
||||
id: partConfig.id,
|
||||
number: partConfig.number,
|
||||
title: partConfig.title,
|
||||
subtitle: partConfig.subtitle,
|
||||
chapters: []
|
||||
}
|
||||
|
||||
for (const chapterConfig of partConfig.chapters) {
|
||||
const chapter: Chapter = {
|
||||
id: chapterConfig.id,
|
||||
title: chapterConfig.title,
|
||||
sections: []
|
||||
}
|
||||
|
||||
const chapterPath = path.join(bookPath, partConfig.folder, chapterConfig.folder)
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(chapterPath)
|
||||
const mdFiles = files.filter(f => f.endsWith('.md')).sort()
|
||||
|
||||
for (const file of mdFiles) {
|
||||
// 从文件名提取ID和标题
|
||||
const match = file.match(/^(\d+\.\d+)\s+(.+)\.md$/)
|
||||
if (match) {
|
||||
const [, id, title] = match
|
||||
const filePath = path.join(chapterPath, file)
|
||||
|
||||
chapter.sections.push({
|
||||
id,
|
||||
title: title.replace(/[::]/g, ':'), // 统一冒号格式
|
||||
isFree: FREE_SECTIONS.includes(id),
|
||||
price: 1,
|
||||
filePath
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 按ID数字排序
|
||||
chapter.sections.sort((a, b) => {
|
||||
const [aMajor, aMinor] = a.id.split('.').map(Number)
|
||||
const [bMajor, bMinor] = b.id.split('.').map(Number)
|
||||
return aMajor !== bMajor ? aMajor - bMajor : aMinor - bMinor
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
console.error(`读取章节目录失败: ${chapterPath}`, e)
|
||||
}
|
||||
|
||||
part.chapters.push(chapter)
|
||||
}
|
||||
|
||||
parts.push(part)
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
// 读取文章内容
|
||||
function getArticleContent(sectionId: string): string | null {
|
||||
const bookData = loadBookData()
|
||||
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
const section = chapter.sections.find(s => s.id === sectionId)
|
||||
if (section?.filePath) {
|
||||
try {
|
||||
return fs.readFileSync(section.filePath, 'utf-8')
|
||||
} catch (e) {
|
||||
console.error(`读取文章内容失败: ${section.filePath}`, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// GET - 获取所有章节数据
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const sectionId = searchParams.get('id')
|
||||
const includeContent = searchParams.get('content') === 'true'
|
||||
|
||||
try {
|
||||
// 如果指定了章节ID,返回单篇文章内容
|
||||
if (sectionId) {
|
||||
const content = getArticleContent(sectionId)
|
||||
if (content) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { id: sectionId, content }
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '文章不存在'
|
||||
}, { status: 404 })
|
||||
}
|
||||
}
|
||||
|
||||
// 返回完整书籍结构
|
||||
const bookData = loadBookData()
|
||||
|
||||
// 统计总章节数
|
||||
let totalSections = 0
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
totalSections += chapter.sections.length
|
||||
}
|
||||
}
|
||||
// 加上序言、尾声和3个附录
|
||||
totalSections += 5
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
totalSections,
|
||||
parts: bookData,
|
||||
appendix: [
|
||||
{ id: 'appendix-1', title: '附录1|Soul派对房精选对话', isFree: true },
|
||||
{ id: 'appendix-2', title: '附录2|创业者自检清单', isFree: true },
|
||||
{ id: 'appendix-3', title: '附录3|本书提到的工具和资源', isFree: true }
|
||||
],
|
||||
preface: { id: 'preface', title: '序言|为什么我每天早上6点在Soul开播?', isFree: true },
|
||||
epilogue: { id: 'epilogue', title: '尾声|这本书的真实目的', isFree: true }
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取章节数据失败:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '获取数据失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// POST - 同步章节数据到数据库(预留接口)
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const bookData = loadBookData()
|
||||
|
||||
// 这里可以添加数据库写入逻辑
|
||||
// 目前先返回成功,数据已从文件系统读取
|
||||
|
||||
let totalSections = 0
|
||||
for (const part of bookData) {
|
||||
for (const chapter of part.chapters) {
|
||||
totalSections += chapter.sections.length
|
||||
}
|
||||
}
|
||||
totalSections += 5 // 序言、尾声、3个附录
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '章节数据同步成功',
|
||||
data: {
|
||||
totalSections,
|
||||
partsCount: bookData.length,
|
||||
chaptersCount: bookData.reduce((acc, p) => acc + p.chapters.length, 0)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('同步章节数据失败:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '同步数据失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
96
app/api/db/distribution/route.ts
Normal file
96
app/api/db/distribution/route.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { distributionDB, purchaseDB } from '@/lib/db'
|
||||
|
||||
// 获取分销数据
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const type = searchParams.get('type')
|
||||
const referrerId = searchParams.get('referrer_id')
|
||||
|
||||
// 获取佣金记录
|
||||
if (type === 'commissions') {
|
||||
const commissions = await distributionDB.getAllCommissions()
|
||||
return NextResponse.json({ success: true, commissions })
|
||||
}
|
||||
|
||||
// 获取指定推荐人的绑定
|
||||
if (referrerId) {
|
||||
const bindings = await distributionDB.getBindingsByReferrer(referrerId)
|
||||
return NextResponse.json({ success: true, bindings })
|
||||
}
|
||||
|
||||
// 获取所有绑定关系
|
||||
const bindings = await distributionDB.getAllBindings()
|
||||
return NextResponse.json({ success: true, bindings })
|
||||
} catch (error: any) {
|
||||
console.error('Get distribution data error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 创建绑定或佣金记录
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { type, data } = body
|
||||
|
||||
if (type === 'binding') {
|
||||
const binding = await distributionDB.createBinding({
|
||||
id: `binding_${Date.now()}`,
|
||||
...data,
|
||||
bound_at: new Date().toISOString(),
|
||||
expires_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30天有效期
|
||||
status: 'active'
|
||||
})
|
||||
return NextResponse.json({ success: true, binding })
|
||||
}
|
||||
|
||||
if (type === 'commission') {
|
||||
const commission = await distributionDB.createCommission({
|
||||
id: `commission_${Date.now()}`,
|
||||
...data,
|
||||
status: 'pending'
|
||||
})
|
||||
return NextResponse.json({ success: true, commission })
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '未知操作类型'
|
||||
}, { status: 400 })
|
||||
} catch (error: any) {
|
||||
console.error('Create distribution record error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 更新绑定状态
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { id, status } = body
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少记录ID'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
await distributionDB.updateBindingStatus(id, status)
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error: any) {
|
||||
console.error('Update distribution status error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
16
app/api/db/init/route.ts
Normal file
16
app/api/db/init/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { initDatabase } from '@/lib/db'
|
||||
|
||||
// 初始化数据库表
|
||||
export async function POST() {
|
||||
try {
|
||||
await initDatabase()
|
||||
return NextResponse.json({ success: true, message: '数据库初始化成功' })
|
||||
} catch (error: any) {
|
||||
console.error('Database init error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message || '数据库初始化失败'
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
106
app/api/db/purchases/route.ts
Normal file
106
app/api/db/purchases/route.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { purchaseDB, userDB, distributionDB } from '@/lib/db'
|
||||
|
||||
// 获取购买记录
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const userId = searchParams.get('user_id')
|
||||
|
||||
if (userId) {
|
||||
const purchases = await purchaseDB.getByUserId(userId)
|
||||
return NextResponse.json({ success: true, purchases })
|
||||
}
|
||||
|
||||
const purchases = await purchaseDB.getAll()
|
||||
return NextResponse.json({ success: true, purchases })
|
||||
} catch (error: any) {
|
||||
console.error('Get purchases error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 创建购买记录
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const {
|
||||
user_id,
|
||||
type,
|
||||
section_id,
|
||||
section_title,
|
||||
amount,
|
||||
payment_method,
|
||||
referral_code
|
||||
} = body
|
||||
|
||||
// 创建购买记录
|
||||
const purchase = await purchaseDB.create({
|
||||
id: `purchase_${Date.now()}`,
|
||||
user_id,
|
||||
type,
|
||||
section_id,
|
||||
section_title,
|
||||
amount,
|
||||
payment_method,
|
||||
referral_code,
|
||||
referrer_earnings: 0,
|
||||
status: 'completed'
|
||||
})
|
||||
|
||||
// 更新用户购买状态
|
||||
if (type === 'fullbook') {
|
||||
await userDB.update(user_id, { has_full_book: true })
|
||||
}
|
||||
|
||||
// 处理分销佣金
|
||||
if (referral_code) {
|
||||
// 查找推荐人
|
||||
const users = await userDB.getAll()
|
||||
const referrer = users.find((u: any) => u.referral_code === referral_code)
|
||||
|
||||
if (referrer) {
|
||||
const commissionRate = 0.9 // 90% 佣金
|
||||
const commissionAmount = amount * commissionRate
|
||||
|
||||
// 查找有效的绑定关系
|
||||
const binding = await distributionDB.getActiveBindingByReferee(user_id)
|
||||
|
||||
if (binding) {
|
||||
// 创建佣金记录
|
||||
await distributionDB.createCommission({
|
||||
id: `commission_${Date.now()}`,
|
||||
binding_id: binding.id,
|
||||
referrer_id: referrer.id,
|
||||
referee_id: user_id,
|
||||
order_id: purchase.id,
|
||||
amount,
|
||||
commission_rate: commissionRate * 100,
|
||||
commission_amount: commissionAmount,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
// 更新推荐人收益
|
||||
await userDB.update(referrer.id, {
|
||||
earnings: (referrer.earnings || 0) + commissionAmount,
|
||||
pending_earnings: (referrer.pending_earnings || 0) + commissionAmount
|
||||
})
|
||||
|
||||
// 更新购买记录的推荐人收益
|
||||
purchase.referrer_earnings = commissionAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, purchase })
|
||||
} catch (error: any) {
|
||||
console.error('Create purchase error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
31
app/api/db/settings/route.ts
Normal file
31
app/api/db/settings/route.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { settingsDB } from '@/lib/db'
|
||||
|
||||
// 获取系统设置
|
||||
export async function GET() {
|
||||
try {
|
||||
const settings = await settingsDB.get()
|
||||
return NextResponse.json({ success: true, settings: settings?.data || null })
|
||||
} catch (error: any) {
|
||||
console.error('Get settings error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 保存系统设置
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
await settingsDB.update(body)
|
||||
return NextResponse.json({ success: true, message: '设置已保存' })
|
||||
} catch (error: any) {
|
||||
console.error('Save settings error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
114
app/api/db/users/route.ts
Normal file
114
app/api/db/users/route.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { userDB } from '@/lib/db'
|
||||
|
||||
// 获取所有用户
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const id = searchParams.get('id')
|
||||
const phone = searchParams.get('phone')
|
||||
|
||||
if (id) {
|
||||
const user = await userDB.getById(id)
|
||||
return NextResponse.json({ success: true, user })
|
||||
}
|
||||
|
||||
if (phone) {
|
||||
const user = await userDB.getByPhone(phone)
|
||||
return NextResponse.json({ success: true, user })
|
||||
}
|
||||
|
||||
const users = await userDB.getAll()
|
||||
return NextResponse.json({ success: true, users })
|
||||
} catch (error: any) {
|
||||
console.error('Get users error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { phone, nickname, password, referral_code, referred_by } = body
|
||||
|
||||
// 检查手机号是否已存在
|
||||
const existing = await userDB.getByPhone(phone)
|
||||
if (existing) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '该手机号已注册'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
const user = await userDB.create({
|
||||
id: `user_${Date.now()}`,
|
||||
phone,
|
||||
nickname,
|
||||
password,
|
||||
referral_code: referral_code || `REF${Date.now().toString(36).toUpperCase()}`,
|
||||
referred_by
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true, user })
|
||||
} catch (error: any) {
|
||||
console.error('Create user error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { id, ...updates } = body
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少用户ID'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
await userDB.update(id, updates)
|
||||
const user = await userDB.getById(id)
|
||||
|
||||
return NextResponse.json({ success: true, user })
|
||||
} catch (error: any) {
|
||||
console.error('Update user error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
export async function DELETE(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const id = searchParams.get('id')
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少用户ID'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
await userDB.delete(id)
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error: any) {
|
||||
console.error('Delete user error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
113
app/api/db/withdrawals/route.ts
Normal file
113
app/api/db/withdrawals/route.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withdrawalDB, userDB } from '@/lib/db'
|
||||
|
||||
// 获取提现记录
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url)
|
||||
const userId = searchParams.get('user_id')
|
||||
|
||||
if (userId) {
|
||||
const withdrawals = await withdrawalDB.getByUserId(userId)
|
||||
return NextResponse.json({ success: true, withdrawals })
|
||||
}
|
||||
|
||||
const withdrawals = await withdrawalDB.getAll()
|
||||
return NextResponse.json({ success: true, withdrawals })
|
||||
} catch (error: any) {
|
||||
console.error('Get withdrawals error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 创建提现申请
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { user_id, amount, method, account, name } = body
|
||||
|
||||
// 验证用户余额
|
||||
const user = await userDB.getById(user_id)
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '用户不存在'
|
||||
}, { status: 404 })
|
||||
}
|
||||
|
||||
if ((user.earnings || 0) < amount) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '余额不足'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
// 创建提现记录
|
||||
const withdrawal = await withdrawalDB.create({
|
||||
id: `withdrawal_${Date.now()}`,
|
||||
user_id,
|
||||
amount,
|
||||
method,
|
||||
account,
|
||||
name,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
// 扣除用户余额,增加待提现金额
|
||||
await userDB.update(user_id, {
|
||||
earnings: (user.earnings || 0) - amount,
|
||||
pending_earnings: (user.pending_earnings || 0) + amount
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true, withdrawal })
|
||||
} catch (error: any) {
|
||||
console.error('Create withdrawal error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
// 更新提现状态
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { id, status } = body
|
||||
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '缺少提现记录ID'
|
||||
}, { status: 400 })
|
||||
}
|
||||
|
||||
await withdrawalDB.updateStatus(id, status)
|
||||
|
||||
// 如果状态是已完成,更新用户的已提现金额
|
||||
if (status === 'completed') {
|
||||
const withdrawals = await withdrawalDB.getAll()
|
||||
const withdrawal = withdrawals.find((w: any) => w.id === id)
|
||||
if (withdrawal) {
|
||||
const user = await userDB.getById(withdrawal.user_id)
|
||||
if (user) {
|
||||
await userDB.update(user.id, {
|
||||
pending_earnings: (user.pending_earnings || 0) - withdrawal.amount,
|
||||
withdrawn_earnings: (user.withdrawn_earnings || 0) + withdrawal.amount
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error: any) {
|
||||
console.error('Update withdrawal status error:', error)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message
|
||||
}, { status: 500 })
|
||||
}
|
||||
}
|
||||
108
app/api/distribution/auto-withdraw-config/route.ts
Normal file
108
app/api/distribution/auto-withdraw-config/route.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 自动提现配置API
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
// 内存存储(实际应用中应该存入数据库)
|
||||
const autoWithdrawConfigs: Map<string, {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
minAmount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}> = new Map();
|
||||
|
||||
// GET: 获取用户自动提现配置
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const config = autoWithdrawConfigs.get(userId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
config: config || null,
|
||||
});
|
||||
}
|
||||
|
||||
// POST: 保存/更新自动提现配置
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { userId, enabled, minAmount, method, account, name } = body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
if (enabled) {
|
||||
if (!minAmount || minAmount < 10) {
|
||||
return NextResponse.json({ error: '最低提现金额不能少于10元' }, { status: 400 });
|
||||
}
|
||||
if (!account) {
|
||||
return NextResponse.json({ error: '请填写提现账号' }, { status: 400 });
|
||||
}
|
||||
if (!name) {
|
||||
return NextResponse.json({ error: '请填写真实姓名' }, { status: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const existingConfig = autoWithdrawConfigs.get(userId);
|
||||
|
||||
const config = {
|
||||
userId,
|
||||
enabled: Boolean(enabled),
|
||||
minAmount: Number(minAmount) || 100,
|
||||
method: method || 'wechat',
|
||||
account: account || '',
|
||||
name: name || '',
|
||||
createdAt: existingConfig?.createdAt || now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
autoWithdrawConfigs.set(userId, config);
|
||||
|
||||
console.log('[AutoWithdrawConfig] 保存配置:', {
|
||||
userId,
|
||||
enabled: config.enabled,
|
||||
minAmount: config.minAmount,
|
||||
method: config.method,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
config,
|
||||
message: enabled ? '自动提现已启用' : '自动提现已关闭',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[AutoWithdrawConfig] 保存失败:', error);
|
||||
return NextResponse.json({ error: '保存配置失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: 删除自动提现配置
|
||||
export async function DELETE(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
autoWithdrawConfigs.delete(userId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '配置已删除',
|
||||
});
|
||||
}
|
||||
53
app/api/distribution/messages/route.ts
Normal file
53
app/api/distribution/messages/route.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 分销消息API
|
||||
* 用于WebSocket轮询获取实时消息
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getMessages, clearMessages } from '@/lib/modules/distribution/websocket';
|
||||
|
||||
// GET: 获取用户消息
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const userId = searchParams.get('userId');
|
||||
const since = searchParams.get('since');
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = getMessages(userId, since || undefined);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
messages,
|
||||
count: messages.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MessagesAPI] 获取消息失败:', error);
|
||||
return NextResponse.json({ error: '获取消息失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST: 标记消息已读
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { userId, messageIds } = body;
|
||||
|
||||
if (!userId || !messageIds || !Array.isArray(messageIds)) {
|
||||
return NextResponse.json({ error: '参数错误' }, { status: 400 });
|
||||
}
|
||||
|
||||
clearMessages(userId, messageIds);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '消息已标记为已读',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MessagesAPI] 标记已读失败:', error);
|
||||
return NextResponse.json({ error: '操作失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
885
app/api/distribution/route.ts
Normal file
885
app/api/distribution/route.ts
Normal file
@@ -0,0 +1,885 @@
|
||||
/**
|
||||
* 分销模块API
|
||||
* 功能:绑定追踪、提现管理、统计概览
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
// 模拟数据存储(实际项目应使用数据库)
|
||||
let distributionBindings: Array<{
|
||||
id: string;
|
||||
referrerId: string;
|
||||
referrerCode: string;
|
||||
visitorId: string;
|
||||
visitorPhone?: string;
|
||||
visitorNickname?: string;
|
||||
bindingTime: string;
|
||||
expireTime: string;
|
||||
status: 'active' | 'converted' | 'expired' | 'cancelled';
|
||||
convertedAt?: string;
|
||||
orderId?: string;
|
||||
orderAmount?: number;
|
||||
commission?: number;
|
||||
source: 'link' | 'miniprogram' | 'poster' | 'qrcode';
|
||||
createdAt: string;
|
||||
}> = [];
|
||||
|
||||
let clickRecords: Array<{
|
||||
id: string;
|
||||
referralCode: string;
|
||||
referrerId: string;
|
||||
visitorId: string;
|
||||
source: string;
|
||||
clickTime: string;
|
||||
}> = [];
|
||||
|
||||
let distributors: Array<{
|
||||
id: string;
|
||||
userId: string;
|
||||
nickname: string;
|
||||
phone: string;
|
||||
referralCode: string;
|
||||
totalClicks: number;
|
||||
totalBindings: number;
|
||||
activeBindings: number;
|
||||
convertedBindings: number;
|
||||
expiredBindings: number;
|
||||
totalEarnings: number;
|
||||
pendingEarnings: number;
|
||||
withdrawnEarnings: number;
|
||||
autoWithdraw: boolean;
|
||||
autoWithdrawThreshold: number;
|
||||
autoWithdrawAccount?: {
|
||||
type: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
};
|
||||
level: 'normal' | 'silver' | 'gold' | 'diamond';
|
||||
commissionRate: number;
|
||||
status: 'active' | 'frozen' | 'disabled';
|
||||
createdAt: string;
|
||||
}> = [];
|
||||
|
||||
let withdrawRecords: Array<{
|
||||
id: string;
|
||||
distributorId: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
actualAmount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
accountName: string;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed' | 'rejected';
|
||||
isAuto: boolean;
|
||||
paymentNo?: string;
|
||||
paymentTime?: string;
|
||||
reviewNote?: string;
|
||||
createdAt: string;
|
||||
completedAt?: string;
|
||||
}> = [];
|
||||
|
||||
// 配置
|
||||
const BINDING_DAYS = 30;
|
||||
const MIN_WITHDRAW_AMOUNT = 10;
|
||||
const DEFAULT_COMMISSION_RATE = 90;
|
||||
|
||||
// 生成ID
|
||||
function generateId(prefix: string = ''): string {
|
||||
return `${prefix}${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
// GET: 获取分销数据
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const type = searchParams.get('type') || 'overview';
|
||||
const userId = searchParams.get('userId');
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
const pageSize = parseInt(searchParams.get('pageSize') || '20');
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'overview':
|
||||
return getOverview();
|
||||
|
||||
case 'bindings':
|
||||
return getBindings(userId, page, pageSize);
|
||||
|
||||
case 'my-bindings':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getMyBindings(userId);
|
||||
|
||||
case 'withdrawals':
|
||||
return getWithdrawals(userId, page, pageSize);
|
||||
|
||||
case 'reminders':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getReminders(userId);
|
||||
|
||||
case 'distributor':
|
||||
if (!userId) {
|
||||
return NextResponse.json({ error: '缺少用户ID' }, { status: 400 });
|
||||
}
|
||||
return getDistributor(userId);
|
||||
|
||||
case 'ranking':
|
||||
return getRanking();
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知类型' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST: 分销操作
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { action } = body;
|
||||
|
||||
switch (action) {
|
||||
case 'record_click':
|
||||
return recordClick(body);
|
||||
|
||||
case 'convert':
|
||||
return convertBinding(body);
|
||||
|
||||
case 'request_withdraw':
|
||||
return requestWithdraw(body);
|
||||
|
||||
case 'set_auto_withdraw':
|
||||
return setAutoWithdraw(body);
|
||||
|
||||
case 'process_expired':
|
||||
return processExpiredBindings();
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: 更新操作(后台管理)
|
||||
export async function PUT(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { action } = body;
|
||||
|
||||
switch (action) {
|
||||
case 'approve_withdraw':
|
||||
return approveWithdraw(body);
|
||||
|
||||
case 'reject_withdraw':
|
||||
return rejectWithdraw(body);
|
||||
|
||||
case 'update_distributor':
|
||||
return updateDistributor(body);
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('分销API错误:', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 具体实现 ==========
|
||||
|
||||
// 获取概览数据
|
||||
function getOverview() {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const weekFromNow = new Date();
|
||||
weekFromNow.setDate(weekFromNow.getDate() + 7);
|
||||
|
||||
const overview = {
|
||||
// 今日数据
|
||||
todayClicks: clickRecords.filter(c => new Date(c.clickTime) >= today).length,
|
||||
todayBindings: distributionBindings.filter(b => new Date(b.createdAt) >= today).length,
|
||||
todayConversions: distributionBindings.filter(b =>
|
||||
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today
|
||||
).length,
|
||||
todayEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= today)
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 本月数据
|
||||
monthClicks: clickRecords.filter(c => new Date(c.clickTime) >= monthStart).length,
|
||||
monthBindings: distributionBindings.filter(b => new Date(b.createdAt) >= monthStart).length,
|
||||
monthConversions: distributionBindings.filter(b =>
|
||||
b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart
|
||||
).length,
|
||||
monthEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted' && b.convertedAt && new Date(b.convertedAt) >= monthStart)
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 总计
|
||||
totalClicks: clickRecords.length,
|
||||
totalBindings: distributionBindings.length,
|
||||
totalConversions: distributionBindings.filter(b => b.status === 'converted').length,
|
||||
totalEarnings: distributionBindings
|
||||
.filter(b => b.status === 'converted')
|
||||
.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
|
||||
// 即将过期
|
||||
expiringBindings: distributionBindings.filter(b =>
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) <= weekFromNow &&
|
||||
new Date(b.expireTime) > now
|
||||
).length,
|
||||
|
||||
// 待处理提现
|
||||
pendingWithdrawals: withdrawRecords.filter(w => w.status === 'pending').length,
|
||||
pendingWithdrawAmount: withdrawRecords
|
||||
.filter(w => w.status === 'pending')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
|
||||
// 转化率
|
||||
conversionRate: clickRecords.length > 0
|
||||
? (distributionBindings.filter(b => b.status === 'converted').length / clickRecords.length * 100).toFixed(2)
|
||||
: '0.00',
|
||||
|
||||
// 分销商数量
|
||||
totalDistributors: distributors.length,
|
||||
activeDistributors: distributors.filter(d => d.status === 'active').length,
|
||||
};
|
||||
|
||||
return NextResponse.json({ success: true, overview });
|
||||
}
|
||||
|
||||
// 获取绑定列表(后台)
|
||||
function getBindings(userId: string | null, page: number, pageSize: number) {
|
||||
let filteredBindings = [...distributionBindings];
|
||||
|
||||
if (userId) {
|
||||
filteredBindings = filteredBindings.filter(b => b.referrerId === userId);
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
filteredBindings.sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
|
||||
const total = filteredBindings.length;
|
||||
const start = (page - 1) * pageSize;
|
||||
const paginatedBindings = filteredBindings.slice(start, start + pageSize);
|
||||
|
||||
// 添加剩余天数
|
||||
const now = new Date();
|
||||
const bindingsWithDays = paginatedBindings.map(b => ({
|
||||
...b,
|
||||
daysRemaining: b.status === 'active'
|
||||
? Math.max(0, Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)))
|
||||
: 0,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
bindings: bindingsWithDays,
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取我的绑定用户(分销中心)
|
||||
function getMyBindings(userId: string) {
|
||||
const myBindings = distributionBindings.filter(b => b.referrerId === userId);
|
||||
const now = new Date();
|
||||
|
||||
// 按状态分类
|
||||
const active = myBindings
|
||||
.filter(b => b.status === 'active')
|
||||
.map(b => ({
|
||||
...b,
|
||||
daysRemaining: Math.max(0, Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24))),
|
||||
}))
|
||||
.sort((a, b) => a.daysRemaining - b.daysRemaining); // 即将过期的排前面
|
||||
|
||||
const converted = myBindings.filter(b => b.status === 'converted');
|
||||
const expired = myBindings.filter(b => b.status === 'expired');
|
||||
|
||||
// 统计
|
||||
const stats = {
|
||||
totalBindings: myBindings.length,
|
||||
activeCount: active.length,
|
||||
convertedCount: converted.length,
|
||||
expiredCount: expired.length,
|
||||
expiringCount: active.filter(b => b.daysRemaining <= 7).length,
|
||||
totalCommission: converted.reduce((sum, b) => sum + (b.commission || 0), 0),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
bindings: {
|
||||
active,
|
||||
converted,
|
||||
expired,
|
||||
},
|
||||
stats,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取提现记录
|
||||
function getWithdrawals(userId: string | null, page: number, pageSize: number) {
|
||||
let filteredWithdrawals = [...withdrawRecords];
|
||||
|
||||
if (userId) {
|
||||
filteredWithdrawals = filteredWithdrawals.filter(w => w.userId === userId);
|
||||
}
|
||||
|
||||
// 按创建时间倒序
|
||||
filteredWithdrawals.sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
|
||||
const total = filteredWithdrawals.length;
|
||||
const start = (page - 1) * pageSize;
|
||||
const paginatedWithdrawals = filteredWithdrawals.slice(start, start + pageSize);
|
||||
|
||||
// 统计
|
||||
const stats = {
|
||||
pending: filteredWithdrawals.filter(w => w.status === 'pending').length,
|
||||
pendingAmount: filteredWithdrawals
|
||||
.filter(w => w.status === 'pending')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
completed: filteredWithdrawals.filter(w => w.status === 'completed').length,
|
||||
completedAmount: filteredWithdrawals
|
||||
.filter(w => w.status === 'completed')
|
||||
.reduce((sum, w) => sum + w.amount, 0),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
withdrawals: paginatedWithdrawals,
|
||||
stats,
|
||||
pagination: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取提醒
|
||||
function getReminders(userId: string) {
|
||||
const now = new Date();
|
||||
const weekFromNow = new Date();
|
||||
weekFromNow.setDate(weekFromNow.getDate() + 7);
|
||||
|
||||
const myBindings = distributionBindings.filter(b =>
|
||||
b.referrerId === userId && b.status === 'active'
|
||||
);
|
||||
|
||||
const expiringSoon = myBindings.filter(b => {
|
||||
const expireTime = new Date(b.expireTime);
|
||||
return expireTime <= weekFromNow && expireTime > now;
|
||||
}).map(b => ({
|
||||
type: 'expiring_soon',
|
||||
binding: b,
|
||||
daysRemaining: Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24)),
|
||||
message: `用户 ${b.visitorNickname || b.visitorPhone || '未知'} 的绑定将在 ${Math.ceil((new Date(b.expireTime).getTime() - now.getTime()) / (1000 * 60 * 60 * 24))} 天后过期`,
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
reminders: expiringSoon,
|
||||
count: expiringSoon.length,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取分销商信息
|
||||
function getDistributor(userId: string) {
|
||||
const distributor = distributors.find(d => d.userId === userId);
|
||||
|
||||
if (!distributor) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
distributor: null,
|
||||
message: '用户尚未成为分销商',
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true, distributor });
|
||||
}
|
||||
|
||||
// 获取排行榜
|
||||
function getRanking() {
|
||||
const ranking = [...distributors]
|
||||
.filter(d => d.status === 'active')
|
||||
.sort((a, b) => b.totalEarnings - a.totalEarnings)
|
||||
.slice(0, 10)
|
||||
.map((d, index) => ({
|
||||
rank: index + 1,
|
||||
distributorId: d.id,
|
||||
nickname: d.nickname,
|
||||
totalEarnings: d.totalEarnings,
|
||||
totalConversions: d.convertedBindings,
|
||||
level: d.level,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ success: true, ranking });
|
||||
}
|
||||
|
||||
// 记录点击
|
||||
function recordClick(body: {
|
||||
referralCode: string;
|
||||
referrerId: string;
|
||||
visitorId: string;
|
||||
visitorPhone?: string;
|
||||
visitorNickname?: string;
|
||||
source: 'link' | 'miniprogram' | 'poster' | 'qrcode';
|
||||
}) {
|
||||
const now = new Date();
|
||||
|
||||
// 1. 记录点击
|
||||
const click = {
|
||||
id: generateId('click_'),
|
||||
referralCode: body.referralCode,
|
||||
referrerId: body.referrerId,
|
||||
visitorId: body.visitorId,
|
||||
source: body.source,
|
||||
clickTime: now.toISOString(),
|
||||
};
|
||||
clickRecords.push(click);
|
||||
|
||||
// 2. 检查现有绑定
|
||||
const existingBinding = distributionBindings.find(b =>
|
||||
b.visitorId === body.visitorId &&
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) > now
|
||||
);
|
||||
|
||||
if (existingBinding) {
|
||||
// 已有有效绑定,只记录点击
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '点击已记录,用户已被其他分销商绑定',
|
||||
click,
|
||||
binding: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 创建新绑定
|
||||
const expireDate = new Date(now);
|
||||
expireDate.setDate(expireDate.getDate() + BINDING_DAYS);
|
||||
|
||||
const binding = {
|
||||
id: generateId('bind_'),
|
||||
referrerId: body.referrerId,
|
||||
referrerCode: body.referralCode,
|
||||
visitorId: body.visitorId,
|
||||
visitorPhone: body.visitorPhone,
|
||||
visitorNickname: body.visitorNickname,
|
||||
bindingTime: now.toISOString(),
|
||||
expireTime: expireDate.toISOString(),
|
||||
status: 'active' as const,
|
||||
source: body.source,
|
||||
createdAt: now.toISOString(),
|
||||
};
|
||||
distributionBindings.push(binding);
|
||||
|
||||
// 4. 更新分销商统计
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.referrerId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].totalClicks++;
|
||||
distributors[distributorIndex].totalBindings++;
|
||||
distributors[distributorIndex].activeBindings++;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '点击已记录,绑定创建成功',
|
||||
click,
|
||||
binding,
|
||||
expireTime: expireDate.toISOString(),
|
||||
bindingDays: BINDING_DAYS,
|
||||
});
|
||||
}
|
||||
|
||||
// 转化绑定(用户付款)
|
||||
function convertBinding(body: {
|
||||
visitorId: string;
|
||||
orderId: string;
|
||||
orderAmount: number;
|
||||
}) {
|
||||
const now = new Date();
|
||||
|
||||
// 查找有效绑定
|
||||
const bindingIndex = distributionBindings.findIndex(b =>
|
||||
b.visitorId === body.visitorId &&
|
||||
b.status === 'active' &&
|
||||
new Date(b.expireTime) > now
|
||||
);
|
||||
|
||||
if (bindingIndex === -1) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: '未找到有效绑定,该订单不计入分销',
|
||||
});
|
||||
}
|
||||
|
||||
const binding = distributionBindings[bindingIndex];
|
||||
|
||||
// 查找分销商
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === binding.referrerId);
|
||||
const commissionRate = distributorIndex !== -1
|
||||
? distributors[distributorIndex].commissionRate
|
||||
: DEFAULT_COMMISSION_RATE;
|
||||
|
||||
const commission = body.orderAmount * (commissionRate / 100);
|
||||
|
||||
// 更新绑定
|
||||
distributionBindings[bindingIndex] = {
|
||||
...binding,
|
||||
status: 'converted',
|
||||
convertedAt: now.toISOString(),
|
||||
orderId: body.orderId,
|
||||
orderAmount: body.orderAmount,
|
||||
commission,
|
||||
};
|
||||
|
||||
// 更新分销商
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].activeBindings--;
|
||||
distributors[distributorIndex].convertedBindings++;
|
||||
distributors[distributorIndex].totalEarnings += commission;
|
||||
distributors[distributorIndex].pendingEarnings += commission;
|
||||
|
||||
// 检查是否需要自动提现
|
||||
checkAutoWithdraw(distributors[distributorIndex]);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '订单转化成功',
|
||||
binding: distributionBindings[bindingIndex],
|
||||
commission,
|
||||
referrerId: binding.referrerId,
|
||||
});
|
||||
}
|
||||
|
||||
// 申请提现
|
||||
function requestWithdraw(body: {
|
||||
userId: string;
|
||||
amount: number;
|
||||
method: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
accountName: string;
|
||||
}) {
|
||||
// 查找分销商
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const distributor = distributors[distributorIndex];
|
||||
|
||||
if (body.amount < MIN_WITHDRAW_AMOUNT) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: `最低提现金额为 ${MIN_WITHDRAW_AMOUNT} 元`
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
if (body.amount > distributor.pendingEarnings) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: '提现金额超过可提现余额'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
// 创建提现记录
|
||||
const withdrawal = {
|
||||
id: generateId('withdraw_'),
|
||||
distributorId: distributor.id,
|
||||
userId: body.userId,
|
||||
userName: distributor.nickname,
|
||||
amount: body.amount,
|
||||
fee: 0,
|
||||
actualAmount: body.amount,
|
||||
method: body.method,
|
||||
account: body.account,
|
||||
accountName: body.accountName,
|
||||
status: 'pending' as const,
|
||||
isAuto: false,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
withdrawRecords.push(withdrawal);
|
||||
|
||||
// 扣除待提现金额
|
||||
distributors[distributorIndex].pendingEarnings -= body.amount;
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '提现申请已提交',
|
||||
withdrawal,
|
||||
});
|
||||
}
|
||||
|
||||
// 设置自动提现
|
||||
function setAutoWithdraw(body: {
|
||||
userId: string;
|
||||
enabled: boolean;
|
||||
threshold?: number;
|
||||
account?: {
|
||||
type: 'wechat' | 'alipay';
|
||||
account: string;
|
||||
name: string;
|
||||
};
|
||||
}) {
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
distributors[distributorIndex] = {
|
||||
...distributors[distributorIndex],
|
||||
autoWithdraw: body.enabled,
|
||||
autoWithdrawThreshold: body.threshold || distributors[distributorIndex].autoWithdrawThreshold,
|
||||
autoWithdrawAccount: body.account || distributors[distributorIndex].autoWithdrawAccount,
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: body.enabled ? '自动提现已开启' : '自动提现已关闭',
|
||||
distributor: distributors[distributorIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 处理过期绑定
|
||||
function processExpiredBindings() {
|
||||
const now = new Date();
|
||||
let expiredCount = 0;
|
||||
|
||||
distributionBindings.forEach((binding, index) => {
|
||||
if (binding.status === 'active' && new Date(binding.expireTime) <= now) {
|
||||
distributionBindings[index].status = 'expired';
|
||||
expiredCount++;
|
||||
|
||||
// 更新分销商统计
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === binding.referrerId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].activeBindings--;
|
||||
distributors[distributorIndex].expiredBindings++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `已处理 ${expiredCount} 个过期绑定`,
|
||||
expiredCount,
|
||||
});
|
||||
}
|
||||
|
||||
// 审核通过提现
|
||||
async function approveWithdraw(body: { withdrawalId: string; reviewedBy?: string }) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === body.withdrawalId);
|
||||
|
||||
if (withdrawalIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '提现记录不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
|
||||
if (withdrawal.status !== 'pending') {
|
||||
return NextResponse.json({ success: false, error: '该提现申请已处理' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 更新状态为处理中
|
||||
withdrawRecords[withdrawalIndex].status = 'processing';
|
||||
|
||||
// 模拟打款(实际项目中调用支付接口)
|
||||
try {
|
||||
// 模拟延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 打款成功
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawRecords[withdrawalIndex],
|
||||
status: 'completed',
|
||||
paymentNo: `PAY${Date.now()}`,
|
||||
paymentTime: new Date().toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// 更新分销商已提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].withdrawnEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '打款成功',
|
||||
withdrawal: withdrawRecords[withdrawalIndex],
|
||||
});
|
||||
} catch (error) {
|
||||
// 打款失败,退还金额
|
||||
withdrawRecords[withdrawalIndex].status = 'failed';
|
||||
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: false, error: '打款失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝提现
|
||||
function rejectWithdraw(body: { withdrawalId: string; reason: string; reviewedBy?: string }) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === body.withdrawalId);
|
||||
|
||||
if (withdrawalIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '提现记录不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
|
||||
if (withdrawal.status !== 'pending') {
|
||||
return NextResponse.json({ success: false, error: '该提现申请已处理' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawal,
|
||||
status: 'rejected',
|
||||
reviewNote: body.reason,
|
||||
};
|
||||
|
||||
// 退还金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '提现申请已拒绝',
|
||||
withdrawal: withdrawRecords[withdrawalIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 更新分销商信息
|
||||
function updateDistributor(body: {
|
||||
userId: string;
|
||||
commissionRate?: number;
|
||||
level?: 'normal' | 'silver' | 'gold' | 'diamond';
|
||||
status?: 'active' | 'frozen' | 'disabled';
|
||||
}) {
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === body.userId);
|
||||
|
||||
if (distributorIndex === -1) {
|
||||
return NextResponse.json({ success: false, error: '分销商不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
distributors[distributorIndex] = {
|
||||
...distributors[distributorIndex],
|
||||
...(body.commissionRate !== undefined && { commissionRate: body.commissionRate }),
|
||||
...(body.level && { level: body.level }),
|
||||
...(body.status && { status: body.status }),
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '分销商信息已更新',
|
||||
distributor: distributors[distributorIndex],
|
||||
});
|
||||
}
|
||||
|
||||
// 检查自动提现
|
||||
function checkAutoWithdraw(distributor: typeof distributors[0]) {
|
||||
if (!distributor.autoWithdraw || !distributor.autoWithdrawAccount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (distributor.pendingEarnings >= distributor.autoWithdrawThreshold) {
|
||||
// 创建自动提现记录
|
||||
const withdrawal = {
|
||||
id: generateId('withdraw_'),
|
||||
distributorId: distributor.id,
|
||||
userId: distributor.userId,
|
||||
userName: distributor.nickname,
|
||||
amount: distributor.pendingEarnings,
|
||||
fee: 0,
|
||||
actualAmount: distributor.pendingEarnings,
|
||||
method: distributor.autoWithdrawAccount.type,
|
||||
account: distributor.autoWithdrawAccount.account,
|
||||
accountName: distributor.autoWithdrawAccount.name,
|
||||
status: 'processing' as const,
|
||||
isAuto: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
withdrawRecords.push(withdrawal);
|
||||
|
||||
// 扣除待提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.id === distributor.id);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings = 0;
|
||||
}
|
||||
|
||||
// 模拟打款(实际项目中调用支付接口)
|
||||
processAutoWithdraw(withdrawal.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理自动提现打款
|
||||
async function processAutoWithdraw(withdrawalId: string) {
|
||||
const withdrawalIndex = withdrawRecords.findIndex(w => w.id === withdrawalId);
|
||||
if (withdrawalIndex === -1) return;
|
||||
|
||||
try {
|
||||
// 模拟延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 打款成功
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
withdrawRecords[withdrawalIndex] = {
|
||||
...withdrawal,
|
||||
status: 'completed',
|
||||
paymentNo: `AUTO_PAY${Date.now()}`,
|
||||
paymentTime: new Date().toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// 更新分销商已提现金额
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].withdrawnEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
console.log(`自动提现成功: ${withdrawal.userName}, 金额: ¥${withdrawal.amount}`);
|
||||
} catch (error) {
|
||||
// 打款失败
|
||||
withdrawRecords[withdrawalIndex].status = 'failed';
|
||||
|
||||
const withdrawal = withdrawRecords[withdrawalIndex];
|
||||
const distributorIndex = distributors.findIndex(d => d.userId === withdrawal.userId);
|
||||
if (distributorIndex !== -1) {
|
||||
distributors[distributorIndex].pendingEarnings += withdrawal.amount;
|
||||
}
|
||||
|
||||
console.error(`自动提现失败: ${withdrawal.userName}`);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 订单管理接口
|
||||
* 开发: 卡若
|
||||
* 技术支持: 存客宝
|
||||
*/
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
@@ -13,7 +18,7 @@ export async function GET(request: NextRequest) {
|
||||
// For now, return mock data
|
||||
const orders = []
|
||||
|
||||
console.log("[v0] Fetching orders for user:", userId)
|
||||
console.log("[Karuo] Fetching orders for user:", userId)
|
||||
|
||||
return NextResponse.json({
|
||||
code: 0,
|
||||
@@ -21,7 +26,7 @@ export async function GET(request: NextRequest) {
|
||||
data: orders,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[v0] Get orders error:", error)
|
||||
console.error("[Karuo] Get orders error:", error)
|
||||
return NextResponse.json({ code: 500, message: "服务器错误" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
/**
|
||||
* 支付宝回调通知 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* POST /api/payment/alipay/notify
|
||||
*/
|
||||
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { AlipayService } from "@/lib/payment/alipay"
|
||||
import { PaymentFactory, SignatureError } from "@/lib/payment"
|
||||
|
||||
// 确保网关已注册
|
||||
import "@/lib/payment/alipay"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// 获取表单数据
|
||||
const formData = await request.formData()
|
||||
const params: Record<string, string> = {}
|
||||
|
||||
@@ -10,39 +21,54 @@ export async function POST(request: NextRequest) {
|
||||
params[key] = value.toString()
|
||||
})
|
||||
|
||||
// 初始化支付宝服务
|
||||
const alipay = new AlipayService({
|
||||
appId: process.env.ALIPAY_APP_ID || "wx432c93e275548671",
|
||||
partnerId: process.env.ALIPAY_PARTNER_ID || "2088511801157159",
|
||||
key: process.env.ALIPAY_KEY || "lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp",
|
||||
returnUrl: "",
|
||||
notifyUrl: "",
|
||||
console.log("[Alipay Notify] 收到回调:", {
|
||||
out_trade_no: params.out_trade_no,
|
||||
trade_status: params.trade_status,
|
||||
total_amount: params.total_amount,
|
||||
})
|
||||
|
||||
// 验证签名
|
||||
const isValid = alipay.verifySign(params)
|
||||
// 创建支付宝网关
|
||||
const gateway = PaymentFactory.create("alipay_wap")
|
||||
|
||||
if (!isValid) {
|
||||
console.error("[v0] Alipay signature verification failed")
|
||||
return new NextResponse("fail")
|
||||
try {
|
||||
// 解析并验证回调数据
|
||||
const notifyResult = gateway.parseNotify(params)
|
||||
|
||||
if (notifyResult.status === "paid") {
|
||||
console.log("[Alipay Notify] 支付成功:", {
|
||||
tradeSn: notifyResult.tradeSn,
|
||||
platformSn: notifyResult.platformSn,
|
||||
amount: notifyResult.payAmount / 100, // 转换为元
|
||||
payTime: notifyResult.payTime,
|
||||
})
|
||||
|
||||
// TODO: 更新订单状态
|
||||
// await OrderService.updateStatus(notifyResult.tradeSn, 'paid')
|
||||
|
||||
// TODO: 解锁内容/开通权限
|
||||
// await ContentService.unlockForUser(notifyResult.attach?.userId, notifyResult.attach?.productId)
|
||||
|
||||
// TODO: 分配佣金(如果有推荐人)
|
||||
// if (notifyResult.attach?.referralCode) {
|
||||
// await ReferralService.distributeCommission(notifyResult)
|
||||
// }
|
||||
} else {
|
||||
console.log("[Alipay Notify] 非支付成功状态:", notifyResult.status)
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
return new NextResponse(gateway.successResponse())
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof SignatureError) {
|
||||
console.error("[Alipay Notify] 签名验证失败")
|
||||
return new NextResponse(gateway.failResponse())
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
const { out_trade_no, trade_status, buyer_id, total_amount } = params
|
||||
|
||||
// 只处理支付成功的通知
|
||||
if (trade_status === "TRADE_SUCCESS" || trade_status === "TRADE_FINISHED") {
|
||||
console.log("[v0] Alipay payment success:", {
|
||||
orderId: out_trade_no,
|
||||
amount: total_amount,
|
||||
buyerId: buyer_id,
|
||||
})
|
||||
|
||||
// TODO: 更新订单状态、解锁内容、分配佣金
|
||||
}
|
||||
|
||||
return new NextResponse("success")
|
||||
} catch (error) {
|
||||
console.error("[v0] Alipay notify error:", error)
|
||||
console.error("[Alipay Notify] 处理失败:", error)
|
||||
return new NextResponse("fail")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 支付回调接口
|
||||
* 开发: 卡若
|
||||
* 技术支持: 存客宝
|
||||
*/
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -5,7 +10,7 @@ export async function POST(request: NextRequest) {
|
||||
const body = await request.json()
|
||||
const { orderId, status, transactionId, amount, paymentMethod, signature } = body
|
||||
|
||||
console.log("[v0] Payment callback received:", {
|
||||
console.log("[Karuo] Payment callback received:", {
|
||||
orderId,
|
||||
status,
|
||||
transactionId,
|
||||
@@ -32,7 +37,7 @@ export async function POST(request: NextRequest) {
|
||||
// Update order status
|
||||
if (status === "success") {
|
||||
// Grant access
|
||||
console.log("[v0] Payment successful, granting access for order:", orderId)
|
||||
console.log("[Karuo] Payment successful, granting access for order:", orderId)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -40,7 +45,7 @@ export async function POST(request: NextRequest) {
|
||||
message: "回调处理成功",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[v0] Payment callback error:", error)
|
||||
console.error("[Karuo] Payment callback error:", error)
|
||||
return NextResponse.json({ code: 500, message: "服务器错误" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
/**
|
||||
* 创建支付订单 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* POST /api/payment/create-order
|
||||
*/
|
||||
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { AlipayService } from "@/lib/payment/alipay"
|
||||
import { WechatPayService } from "@/lib/payment/wechat"
|
||||
import {
|
||||
PaymentFactory,
|
||||
generateOrderSn,
|
||||
generateTradeSn,
|
||||
yuanToFen,
|
||||
getNotifyUrl,
|
||||
getReturnUrl,
|
||||
} from "@/lib/payment"
|
||||
|
||||
// 确保网关已注册
|
||||
import "@/lib/payment/alipay"
|
||||
import "@/lib/payment/wechat"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { userId, type, sectionId, sectionTitle, amount, paymentMethod, referralCode } = body
|
||||
|
||||
// Validate required fields
|
||||
// 验证必要参数
|
||||
if (!userId || !type || !amount || !paymentMethod) {
|
||||
return NextResponse.json({ code: 400, message: "缺少必要参数" }, { status: 400 })
|
||||
return NextResponse.json(
|
||||
{ code: 400, message: "缺少必要参数", data: null },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Generate order ID
|
||||
const orderId = `ORDER_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
// 生成订单号
|
||||
const orderSn = generateOrderSn()
|
||||
const tradeSn = generateTradeSn()
|
||||
|
||||
// Create order object
|
||||
// 创建订单对象
|
||||
const order = {
|
||||
orderId,
|
||||
orderSn,
|
||||
tradeSn,
|
||||
userId,
|
||||
type, // "section" | "fullbook"
|
||||
sectionId: type === "section" ? sectionId : undefined,
|
||||
@@ -25,64 +47,83 @@ export async function POST(request: NextRequest) {
|
||||
amount,
|
||||
paymentMethod, // "wechat" | "alipay" | "usdt" | "paypal"
|
||||
referralCode,
|
||||
status: "pending", // pending | completed | failed | refunded
|
||||
status: "created",
|
||||
createdAt: new Date().toISOString(),
|
||||
expireAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30 minutes
|
||||
expireAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30分钟
|
||||
}
|
||||
|
||||
// According to the payment method, create a payment order
|
||||
// 获取客户端IP
|
||||
const clientIp = request.headers.get("x-forwarded-for")
|
||||
|| request.headers.get("x-real-ip")
|
||||
|| "127.0.0.1"
|
||||
|
||||
// 根据支付方式创建支付网关
|
||||
let paymentData = null
|
||||
const goodsTitle = type === "section" ? `购买章节: ${sectionTitle}` : "购买整本书"
|
||||
|
||||
// 确定网关类型
|
||||
let gateway: string
|
||||
if (paymentMethod === "alipay") {
|
||||
const alipay = new AlipayService({
|
||||
appId: process.env.ALIPAY_APP_ID || "wx432c93e275548671",
|
||||
partnerId: process.env.ALIPAY_PARTNER_ID || "2088511801157159",
|
||||
key: process.env.ALIPAY_KEY || "lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp",
|
||||
returnUrl: process.env.ALIPAY_RETURN_URL || `${process.env.NEXT_PUBLIC_BASE_URL}/payment/success`,
|
||||
notifyUrl: process.env.ALIPAY_NOTIFY_URL || `${process.env.NEXT_PUBLIC_BASE_URL}/api/payment/alipay/notify`,
|
||||
})
|
||||
|
||||
paymentData = alipay.createOrder({
|
||||
outTradeNo: orderId,
|
||||
subject: type === "section" ? `购买章节: ${sectionTitle}` : "购买整本书",
|
||||
totalAmount: amount,
|
||||
body: `知识付费-书籍购买`,
|
||||
})
|
||||
gateway = "alipay_wap"
|
||||
} else if (paymentMethod === "wechat") {
|
||||
const wechat = new WechatPayService({
|
||||
appId: process.env.WECHAT_APP_ID || "wx432c93e275548671",
|
||||
appSecret: process.env.WECHAT_APP_SECRET || "25b7e7fdb7998e5107e242ebb6ddabd0",
|
||||
mchId: process.env.WECHAT_MCH_ID || "1318592501",
|
||||
apiKey: process.env.WECHAT_API_KEY || "wx3e31b068be59ddc131b068be59ddc2",
|
||||
notifyUrl: process.env.WECHAT_NOTIFY_URL || `${process.env.NEXT_PUBLIC_BASE_URL}/api/payment/wechat/notify`,
|
||||
gateway = "wechat_native"
|
||||
} else {
|
||||
gateway = paymentMethod
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建支付网关
|
||||
const paymentGateway = PaymentFactory.create(gateway)
|
||||
|
||||
// 创建交易
|
||||
const tradeResult = await paymentGateway.createTrade({
|
||||
goodsTitle,
|
||||
goodsDetail: `知识付费-书籍购买`,
|
||||
tradeSn,
|
||||
orderSn,
|
||||
amount: yuanToFen(amount),
|
||||
notifyUrl: getNotifyUrl(paymentMethod === "wechat" ? "wechat" : "alipay"),
|
||||
returnUrl: getReturnUrl(orderSn),
|
||||
createIp: clientIp.split(",")[0].trim(),
|
||||
platformType: paymentMethod === "wechat" ? "native" : "wap",
|
||||
})
|
||||
|
||||
const clientIp = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "127.0.0.1"
|
||||
paymentData = {
|
||||
type: tradeResult.type,
|
||||
payload: tradeResult.payload,
|
||||
tradeSn: tradeResult.tradeSn,
|
||||
expiration: tradeResult.expiration,
|
||||
}
|
||||
|
||||
paymentData = await wechat.createOrder({
|
||||
outTradeNo: orderId,
|
||||
body: type === "section" ? `购买章节: ${sectionTitle}` : "购买整本书",
|
||||
totalFee: amount,
|
||||
spbillCreateIp: clientIp.split(",")[0].trim(),
|
||||
})
|
||||
} catch (gatewayError) {
|
||||
console.error("[Payment] Gateway error:", gatewayError)
|
||||
// 如果网关创建失败,返回模拟数据(开发测试用)
|
||||
paymentData = {
|
||||
type: "qrcode",
|
||||
payload: `mock://pay/${paymentMethod}/${orderSn}`,
|
||||
tradeSn,
|
||||
expiration: 1800,
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 0,
|
||||
code: 200,
|
||||
message: "订单创建成功",
|
||||
data: {
|
||||
...order,
|
||||
paymentData,
|
||||
gateway, // 返回网关信息,用于前端轮询时指定
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[v0] Create order error:", error)
|
||||
console.error("[Payment] Create order error:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
message: error instanceof Error ? error.message : "服务器错误",
|
||||
data: null,
|
||||
},
|
||||
{ status: 500 },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
37
app/api/payment/methods/route.ts
Normal file
37
app/api/payment/methods/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 获取可用支付方式 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* GET /api/payment/methods
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server"
|
||||
import { PaymentFactory } from "@/lib/payment"
|
||||
|
||||
// 确保网关已注册
|
||||
import "@/lib/payment/alipay"
|
||||
import "@/lib/payment/wechat"
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const methods = PaymentFactory.getEnabledGateways()
|
||||
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
methods,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[Payment] Get methods error:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
message: error instanceof Error ? error.message : "服务器错误",
|
||||
data: null,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
143
app/api/payment/query/route.ts
Normal file
143
app/api/payment/query/route.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 查询支付状态 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* GET /api/payment/query?tradeSn=xxx&gateway=wechat_native|alipay_wap
|
||||
*/
|
||||
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { PaymentFactory } from "@/lib/payment"
|
||||
|
||||
// 确保网关已注册
|
||||
import "@/lib/payment/alipay"
|
||||
import "@/lib/payment/wechat"
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const tradeSn = searchParams.get("tradeSn")
|
||||
const gateway = searchParams.get("gateway") // 可选:指定支付网关
|
||||
|
||||
if (!tradeSn) {
|
||||
return NextResponse.json(
|
||||
{ code: 400, message: "缺少交易号", data: null },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
console.log("[Payment Query] 查询交易状态:", { tradeSn, gateway })
|
||||
|
||||
let wechatResult = null
|
||||
let alipayResult = null
|
||||
|
||||
// 如果指定了网关,只查询该网关
|
||||
if (gateway) {
|
||||
try {
|
||||
const paymentGateway = PaymentFactory.create(gateway)
|
||||
const result = await paymentGateway.queryTrade(tradeSn)
|
||||
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
tradeSn: result?.tradeSn || tradeSn,
|
||||
status: result?.status || "paying",
|
||||
platformSn: result?.platformSn || null,
|
||||
payAmount: result?.payAmount || null,
|
||||
payTime: result?.payTime || null,
|
||||
gateway,
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(`[Payment Query] ${gateway} 查询失败:`, e)
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
tradeSn,
|
||||
status: "paying",
|
||||
platformSn: null,
|
||||
payAmount: null,
|
||||
payTime: null,
|
||||
gateway,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 没有指定网关时,先查询微信
|
||||
try {
|
||||
const wechatGateway = PaymentFactory.create("wechat_native")
|
||||
wechatResult = await wechatGateway.queryTrade(tradeSn)
|
||||
|
||||
if (wechatResult && wechatResult.status === "paid") {
|
||||
console.log("[Payment Query] 微信支付成功:", { tradeSn, status: wechatResult.status })
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
tradeSn: wechatResult.tradeSn,
|
||||
status: wechatResult.status,
|
||||
platformSn: wechatResult.platformSn,
|
||||
payAmount: wechatResult.payAmount,
|
||||
payTime: wechatResult.payTime,
|
||||
gateway: "wechat_native",
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("[Payment Query] 微信查询异常:", e)
|
||||
}
|
||||
|
||||
// 再查询支付宝
|
||||
try {
|
||||
const alipayGateway = PaymentFactory.create("alipay_wap")
|
||||
alipayResult = await alipayGateway.queryTrade(tradeSn)
|
||||
|
||||
if (alipayResult && alipayResult.status === "paid") {
|
||||
console.log("[Payment Query] 支付宝支付成功:", { tradeSn, status: alipayResult.status })
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
tradeSn: alipayResult.tradeSn,
|
||||
status: alipayResult.status,
|
||||
platformSn: alipayResult.platformSn,
|
||||
payAmount: alipayResult.payAmount,
|
||||
payTime: alipayResult.payTime,
|
||||
gateway: "alipay_wap",
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("[Payment Query] 支付宝查询异常:", e)
|
||||
}
|
||||
|
||||
// 如果都未支付,优先返回微信的状态(因为更可靠)
|
||||
const result = wechatResult || alipayResult
|
||||
|
||||
// 返回等待支付状态
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: {
|
||||
tradeSn,
|
||||
status: result?.status || "paying",
|
||||
platformSn: result?.platformSn || null,
|
||||
payAmount: result?.payAmount || null,
|
||||
payTime: result?.payTime || null,
|
||||
gateway: null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[Payment Query] Error:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
message: error instanceof Error ? error.message : "服务器错误",
|
||||
data: null,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
53
app/api/payment/status/[orderSn]/route.ts
Normal file
53
app/api/payment/status/[orderSn]/route.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 查询订单支付状态 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* GET /api/payment/status/{orderSn}
|
||||
*/
|
||||
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ orderSn: string }> }
|
||||
) {
|
||||
try {
|
||||
const { orderSn } = await params
|
||||
|
||||
if (!orderSn) {
|
||||
return NextResponse.json(
|
||||
{ code: 400, message: "缺少订单号", data: null },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: 从数据库查询订单状态
|
||||
// const order = await OrderService.getByOrderSn(orderSn)
|
||||
|
||||
// 模拟返回数据(开发测试用)
|
||||
const mockOrder = {
|
||||
orderSn,
|
||||
status: "created", // created | paying | paid | closed | refunded
|
||||
paidAmount: null,
|
||||
paidAt: null,
|
||||
paymentMethod: null,
|
||||
tradeSn: null,
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
message: "success",
|
||||
data: mockOrder,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[Payment] Query status error:", error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
code: 500,
|
||||
message: error instanceof Error ? error.message : "服务器错误",
|
||||
data: null,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 支付验证接口
|
||||
* 开发: 卡若
|
||||
* 技术支持: 存客宝
|
||||
*/
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -11,7 +16,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// In production, verify with payment gateway API
|
||||
// For now, simulate verification
|
||||
console.log("[v0] Verifying payment:", { orderId, paymentMethod, transactionId })
|
||||
console.log("[Karuo] Verifying payment:", { orderId, paymentMethod, transactionId })
|
||||
|
||||
// Simulate verification delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
@@ -40,7 +45,7 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[v0] Verify payment error:", error)
|
||||
console.error("[Karuo] Verify payment error:", error)
|
||||
return NextResponse.json({ code: 500, message: "服务器错误" }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,74 @@
|
||||
/**
|
||||
* 微信支付回调通知 API
|
||||
* 基于 Universal_Payment_Module v4.0 设计
|
||||
*
|
||||
* POST /api/payment/wechat/notify
|
||||
*/
|
||||
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { WechatPayService } from "@/lib/payment/wechat"
|
||||
import { PaymentFactory, SignatureError } from "@/lib/payment"
|
||||
|
||||
// 确保网关已注册
|
||||
import "@/lib/payment/wechat"
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// 获取XML原始数据
|
||||
const xmlData = await request.text()
|
||||
|
||||
const wechat = new WechatPayService({
|
||||
appId: process.env.WECHAT_APP_ID || "wx432c93e275548671",
|
||||
appSecret: process.env.WECHAT_APP_SECRET || "25b7e7fdb7998e5107e242ebb6ddabd0",
|
||||
mchId: process.env.WECHAT_MCH_ID || "1318592501",
|
||||
apiKey: process.env.WECHAT_API_KEY || "wx3e31b068be59ddc131b068be59ddc2",
|
||||
notifyUrl: "",
|
||||
})
|
||||
console.log("[Wechat Notify] 收到回调:", xmlData.slice(0, 200))
|
||||
|
||||
// 解析XML数据
|
||||
const params = await wechat["parseXML"](xmlData)
|
||||
// 创建微信支付网关
|
||||
const gateway = PaymentFactory.create("wechat_native")
|
||||
|
||||
// 验证签名
|
||||
const isValid = wechat.verifySign(params)
|
||||
try {
|
||||
// 解析并验证回调数据
|
||||
const notifyResult = gateway.parseNotify(xmlData)
|
||||
|
||||
if (!isValid) {
|
||||
console.error("[v0] WeChat signature verification failed")
|
||||
return new NextResponse(
|
||||
"<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>",
|
||||
{
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
},
|
||||
)
|
||||
}
|
||||
if (notifyResult.status === "paid") {
|
||||
console.log("[Wechat Notify] 支付成功:", {
|
||||
tradeSn: notifyResult.tradeSn,
|
||||
platformSn: notifyResult.platformSn,
|
||||
amount: notifyResult.payAmount / 100, // 转换为元
|
||||
payTime: notifyResult.payTime,
|
||||
})
|
||||
|
||||
const { out_trade_no, result_code, total_fee, openid } = params
|
||||
// TODO: 更新订单状态
|
||||
// await OrderService.updateStatus(notifyResult.tradeSn, 'paid')
|
||||
|
||||
if (result_code === "SUCCESS") {
|
||||
console.log("[v0] WeChat payment success:", {
|
||||
orderId: out_trade_no,
|
||||
amount: Number.parseInt(total_fee) / 100,
|
||||
openid,
|
||||
// TODO: 解锁内容/开通权限
|
||||
// await ContentService.unlockForUser(notifyResult.attach?.userId, notifyResult.attach?.productId)
|
||||
|
||||
// TODO: 分配佣金(如果有推荐人)
|
||||
// if (notifyResult.attach?.referralCode) {
|
||||
// await ReferralService.distributeCommission(notifyResult)
|
||||
// }
|
||||
} else {
|
||||
console.log("[Wechat Notify] 支付失败:", notifyResult)
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
return new NextResponse(gateway.successResponse(), {
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
})
|
||||
|
||||
// TODO: 更新订单状态、解锁内容、分配佣金
|
||||
} catch (error) {
|
||||
if (error instanceof SignatureError) {
|
||||
console.error("[Wechat Notify] 签名验证失败")
|
||||
return new NextResponse(gateway.failResponse(), {
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
})
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
return new NextResponse(
|
||||
"<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>",
|
||||
{
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("[v0] WeChat notify error:", error)
|
||||
return new NextResponse(
|
||||
"<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[系统错误]]></return_msg></xml>",
|
||||
{
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
},
|
||||
)
|
||||
console.error("[Wechat Notify] 处理失败:", error)
|
||||
|
||||
// 返回失败响应
|
||||
const failXml = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[系统错误]]></return_msg></xml>'
|
||||
return new NextResponse(failXml, {
|
||||
headers: { "Content-Type": "application/xml" },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user