更新.gitignore以排除部署配置文件,删除不再使用的一键部署脚本,优化小程序部署流程,增强文档说明。
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
/**
|
||||
* 后台提现管理API
|
||||
* 获取所有提现记录,处理提现审批
|
||||
* 批准时如已配置微信转账则调用「商家转账到零钱」,否则仅更新为成功(需线下打款)
|
||||
*/
|
||||
import { NextResponse } from 'next/server'
|
||||
import { query } from '@/lib/db'
|
||||
import { createTransfer } from '@/lib/wechat-transfer'
|
||||
|
||||
// 获取所有提现记录
|
||||
export async function GET(request: Request) {
|
||||
@@ -112,24 +114,47 @@ export async function PUT(request: Request) {
|
||||
}
|
||||
|
||||
if (action === 'approve') {
|
||||
// 批准提现 - 更新状态为成功
|
||||
const openid = withdrawal.wechat_openid || ''
|
||||
const amountFen = Math.round(parseFloat(withdrawal.amount) * 100)
|
||||
if (openid && amountFen > 0) {
|
||||
const result = await createTransfer({
|
||||
openid,
|
||||
amountFen,
|
||||
outDetailNo: id,
|
||||
transferRemark: 'Soul创业派对-提现',
|
||||
})
|
||||
if (result.success) {
|
||||
await query(`
|
||||
UPDATE withdrawals
|
||||
SET status = 'processing', transaction_id = ?
|
||||
WHERE id = ?
|
||||
`, [result.batchId || result.outBatchNo || '', id])
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '已发起微信转账,等待到账后自动更新状态',
|
||||
batchId: result.batchId,
|
||||
})
|
||||
}
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: result.errorMessage || '微信转账发起失败',
|
||||
}, { status: 400 })
|
||||
}
|
||||
// 无 openid 或金额为 0:仅标记为成功(线下打款)
|
||||
await query(`
|
||||
UPDATE withdrawals
|
||||
SET status = 'success', processed_at = NOW(), transaction_id = ?
|
||||
WHERE id = ?
|
||||
`, [`manual_${Date.now()}`, id])
|
||||
|
||||
// 更新用户已提现金额
|
||||
await query(`
|
||||
UPDATE users
|
||||
SET withdrawn_earnings = withdrawn_earnings + ?,
|
||||
pending_earnings = pending_earnings - ?
|
||||
WHERE id = ?
|
||||
`, [withdrawal.amount, withdrawal.amount, withdrawal.user_id])
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '提现已批准'
|
||||
message: '提现已批准(线下打款)',
|
||||
})
|
||||
|
||||
} else if (action === 'reject') {
|
||||
|
||||
@@ -2,28 +2,63 @@
|
||||
* 订单管理接口
|
||||
* 开发: 卡若
|
||||
* 技术支持: 存客宝
|
||||
*
|
||||
* GET /api/orders - 管理后台:返回全部订单(无 userId)
|
||||
* GET /api/orders?userId= - 按用户返回订单
|
||||
*/
|
||||
import { type NextRequest, NextResponse } from "next/server"
|
||||
import { query } from "@/lib/db"
|
||||
|
||||
function rowToOrder(row: Record<string, unknown>) {
|
||||
return {
|
||||
id: row.id,
|
||||
orderSn: row.order_sn,
|
||||
userId: row.user_id,
|
||||
openId: row.open_id,
|
||||
productType: row.product_type,
|
||||
productId: row.product_id,
|
||||
amount: row.amount,
|
||||
description: row.description,
|
||||
status: row.status,
|
||||
transactionId: row.transaction_id,
|
||||
payTime: row.pay_time,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const userId = searchParams.get("userId")
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json({ code: 400, message: "缺少用户ID" }, { status: 400 })
|
||||
let rows: Record<string, unknown>[] = []
|
||||
try {
|
||||
if (userId) {
|
||||
rows = (await query(
|
||||
"SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC",
|
||||
[userId]
|
||||
)) as Record<string, unknown>[]
|
||||
} else {
|
||||
// 管理后台:无 userId 时返回全部订单
|
||||
rows = (await query(
|
||||
"SELECT * FROM orders ORDER BY created_at DESC"
|
||||
)) as Record<string, unknown>[]
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Karuo] Orders query error:", e)
|
||||
// 表可能未初始化,返回空列表
|
||||
rows = []
|
||||
}
|
||||
|
||||
// In production, fetch from database
|
||||
// For now, return mock data
|
||||
const orders = []
|
||||
|
||||
console.log("[Karuo] Fetching orders for user:", userId)
|
||||
const orders = rows.map(rowToOrder)
|
||||
|
||||
return NextResponse.json({
|
||||
code: 0,
|
||||
message: "获取成功",
|
||||
data: orders,
|
||||
success: true,
|
||||
orders,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[Karuo] Get orders error:", error)
|
||||
|
||||
65
app/api/payment/wechat/transfer/notify/route.ts
Normal file
65
app/api/payment/wechat/transfer/notify/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 微信支付 - 商家转账到零钱 结果通知
|
||||
* 文档: 开发文档/提现功能完整技术文档.md
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { decryptResource } from '@/lib/wechat-transfer'
|
||||
import { query } from '@/lib/db'
|
||||
|
||||
const cfg = {
|
||||
apiV3Key: process.env.WECHAT_API_V3_KEY || process.env.WECHAT_MCH_KEY || '',
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const rawBody = await request.text()
|
||||
const data = JSON.parse(rawBody) as {
|
||||
event_type?: string
|
||||
resource?: { ciphertext: string; nonce: string; associated_data: string }
|
||||
}
|
||||
if (data.event_type !== 'MCHTRANSFER.BILL.FINISHED' || !data.resource) {
|
||||
return NextResponse.json({ code: 'SUCCESS' })
|
||||
}
|
||||
const { ciphertext, nonce, associated_data } = data.resource
|
||||
const decrypted = decryptResource(
|
||||
ciphertext,
|
||||
nonce,
|
||||
associated_data,
|
||||
cfg.apiV3Key
|
||||
) as { out_bill_no?: string; state?: string; transfer_bill_no?: string }
|
||||
const outBillNo = decrypted.out_bill_no
|
||||
const state = decrypted.state
|
||||
const transferBillNo = decrypted.transfer_bill_no || ''
|
||||
if (!outBillNo) {
|
||||
return NextResponse.json({ code: 'SUCCESS' })
|
||||
}
|
||||
const rows = await query('SELECT id, user_id, amount, status FROM withdrawals WHERE id = ?', [outBillNo]) as any[]
|
||||
if (rows.length === 0) {
|
||||
return NextResponse.json({ code: 'SUCCESS' })
|
||||
}
|
||||
const w = rows[0]
|
||||
if (w.status !== 'processing') {
|
||||
return NextResponse.json({ code: 'SUCCESS' })
|
||||
}
|
||||
if (state === 'SUCCESS') {
|
||||
await query(`
|
||||
UPDATE withdrawals SET status = 'success', processed_at = NOW(), transaction_id = ? WHERE id = ?
|
||||
`, [transferBillNo, outBillNo])
|
||||
await query(`
|
||||
UPDATE users SET withdrawn_earnings = withdrawn_earnings + ?, pending_earnings = GREATEST(0, pending_earnings - ?) WHERE id = ?
|
||||
`, [w.amount, w.amount, w.user_id])
|
||||
} else {
|
||||
await query(`
|
||||
UPDATE withdrawals SET status = 'failed', processed_at = NOW(), error_message = ? WHERE id = ?
|
||||
`, [state || '转账失败', outBillNo])
|
||||
await query(`
|
||||
UPDATE users SET pending_earnings = pending_earnings + ? WHERE id = ?
|
||||
`, [w.amount, w.user_id])
|
||||
}
|
||||
return NextResponse.json({ code: 'SUCCESS' })
|
||||
} catch (e) {
|
||||
console.error('[WechatTransferNotify]', e)
|
||||
return NextResponse.json({ code: 'FAIL', message: '处理失败' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -52,15 +52,15 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const user = users[0]
|
||||
|
||||
// 检查是否绑定支付方式(微信号或支付宝)
|
||||
// 如果没有绑定,提示用户先绑定
|
||||
// 微信零钱提现需要 open_id(小程序/公众号登录获得)
|
||||
const openId = user.open_id || ''
|
||||
const wechatId = user.wechat || user.wechat_id || ''
|
||||
const alipayId = user.alipay || ''
|
||||
|
||||
if (!wechatId && !alipayId) {
|
||||
if (!openId && !alipayId) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: '请先在设置中绑定微信号或支付宝',
|
||||
message: '提现到微信零钱需先使用微信登录;或绑定支付宝后提现到支付宝',
|
||||
needBind: true
|
||||
})
|
||||
}
|
||||
@@ -101,20 +101,24 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
}
|
||||
|
||||
// 创建提现记录
|
||||
// 创建提现记录(微信零钱需保存 wechat_openid 供后台批准时调用商家转账到零钱)
|
||||
const withdrawId = `W${Date.now()}`
|
||||
const accountType = alipayId ? 'alipay' : 'wechat'
|
||||
const account = alipayId || wechatId
|
||||
|
||||
try {
|
||||
await query(`
|
||||
INSERT INTO withdrawals (id, user_id, amount, account_type, account, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, 'pending', NOW())
|
||||
`, [withdrawId, userId, amount, accountType, account])
|
||||
INSERT INTO withdrawals (id, user_id, amount, status, wechat_openid, created_at)
|
||||
VALUES (?, ?, ?, 'pending', ?, NOW())
|
||||
`, [withdrawId, userId, amount, accountType === 'wechat' ? openId : null])
|
||||
|
||||
// TODO: 实际调用微信企业付款或支付宝转账API
|
||||
// 这里先模拟成功
|
||||
await query(`UPDATE withdrawals SET status = 'completed', completed_at = NOW() WHERE id = ?`, [withdrawId])
|
||||
// 微信零钱由后台批准时调用「商家转账到零钱」;支付宝/无 openid 时仅标记成功(需线下打款)
|
||||
if (accountType !== 'wechat' || !openId) {
|
||||
await query(`UPDATE withdrawals SET status = 'success', processed_at = NOW() WHERE id = ?`, [withdrawId])
|
||||
await query(`
|
||||
UPDATE users SET withdrawn_earnings = withdrawn_earnings + ?, pending_earnings = GREATEST(0, pending_earnings - ?) WHERE id = ?
|
||||
`, [amount, amount, userId])
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[Withdraw] 创建提现记录失败:', e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user