188 lines
5.0 KiB
TypeScript
188 lines
5.0 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { and, eq } from "drizzle-orm";
|
|
import { auth } from "@/app/(auth)/auth";
|
|
import { db } from "@/lib/db";
|
|
import { paymentOrder, systemSettings } from "@/lib/db/schema";
|
|
import {
|
|
DULUPAY_CONFIG_KEY,
|
|
normalizeDulupayConfig,
|
|
} from "@/lib/payment/dulupay-config";
|
|
import {
|
|
normalizeDulupayBaseUrl,
|
|
signDulupayV1,
|
|
} from "@/lib/payment/dulupay";
|
|
import {
|
|
POINTS_PACKAGES_KEY,
|
|
normalizePointsPackages,
|
|
} from "@/lib/points/config";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
const createSchema = z.object({
|
|
packageId: z.string().trim().min(1).max(64),
|
|
payType: z.string().trim().min(1).max(32).optional(),
|
|
});
|
|
|
|
function buildOutTradeNo() {
|
|
const random = Math.random().toString(36).slice(2, 8);
|
|
return `DP${Date.now()}${random}`.slice(0, 64);
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const session = await auth();
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const body = createSchema.parse(await request.json());
|
|
|
|
const [dulupaySetting, pointsPackagesSetting] = await Promise.all([
|
|
db
|
|
.select()
|
|
.from(systemSettings)
|
|
.where(eq(systemSettings.key, DULUPAY_CONFIG_KEY))
|
|
.limit(1)
|
|
.then((rows) => rows[0]),
|
|
db
|
|
.select()
|
|
.from(systemSettings)
|
|
.where(eq(systemSettings.key, POINTS_PACKAGES_KEY))
|
|
.limit(1)
|
|
.then((rows) => rows[0]),
|
|
]);
|
|
|
|
const dulupayConfig = normalizeDulupayConfig(dulupaySetting?.value);
|
|
if (!dulupayConfig.enabled) {
|
|
return NextResponse.json(
|
|
{ error: "DuluPay 未启用" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (dulupayConfig.version !== "v1") {
|
|
return NextResponse.json(
|
|
{ error: "当前仅支持 DuluPay V1 支付" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (!dulupayConfig.pid || !dulupayConfig.key) {
|
|
return NextResponse.json(
|
|
{ error: "DuluPay 配置不完整,请补充 PID 和 Key" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const packages = normalizePointsPackages(pointsPackagesSetting?.value);
|
|
const selectedPackage = packages.find(
|
|
(item) => item.id === body.packageId && item.isEnabled !== false
|
|
);
|
|
|
|
if (!selectedPackage) {
|
|
return NextResponse.json(
|
|
{ error: "积分套餐不存在或未启用" },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
if (selectedPackage.price <= 0) {
|
|
return NextResponse.json(
|
|
{ error: "该套餐金额无效" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const outTradeNo = buildOutTradeNo();
|
|
const amount = Number(selectedPackage.price.toFixed(2));
|
|
const amountCents = Math.round(amount * 100);
|
|
const payType = body.payType || dulupayConfig.defaultType || "alipay";
|
|
|
|
const defaultOrigin = new URL(request.url).origin;
|
|
const notifyUrl =
|
|
dulupayConfig.notifyUrl || `${defaultOrigin}/api/payment/dulupay/notify`;
|
|
const returnUrl = dulupayConfig.returnUrl || `${defaultOrigin}/`;
|
|
|
|
const baseParams = {
|
|
pid: dulupayConfig.pid,
|
|
type: payType,
|
|
out_trade_no: outTradeNo,
|
|
notify_url: notifyUrl,
|
|
return_url: returnUrl,
|
|
name: `积分充值-${selectedPackage.name}`,
|
|
money: amount.toFixed(2),
|
|
param: `${session.user.id}:${selectedPackage.id}`,
|
|
};
|
|
|
|
const sign = signDulupayV1(baseParams, dulupayConfig.key);
|
|
const params = new URLSearchParams({
|
|
...baseParams,
|
|
sign,
|
|
sign_type: "MD5",
|
|
});
|
|
|
|
const paymentUrl = `${normalizeDulupayBaseUrl(
|
|
dulupayConfig.baseUrl
|
|
)}/submit.php?${params.toString()}`;
|
|
|
|
const existing = await db
|
|
.select({ id: paymentOrder.id })
|
|
.from(paymentOrder)
|
|
.where(
|
|
and(
|
|
eq(paymentOrder.outTradeNo, outTradeNo),
|
|
eq(paymentOrder.provider, "dulupay")
|
|
)
|
|
)
|
|
.limit(1)
|
|
.then((rows) => rows[0]);
|
|
|
|
if (existing) {
|
|
return NextResponse.json({ error: "订单创建失败,请重试" }, { status: 500 });
|
|
}
|
|
|
|
const [created] = await db
|
|
.insert(paymentOrder)
|
|
.values({
|
|
userId: session.user.id,
|
|
provider: "dulupay",
|
|
outTradeNo,
|
|
packageId: selectedPackage.id,
|
|
points: selectedPackage.points,
|
|
amountCents,
|
|
currency: selectedPackage.currency,
|
|
payType,
|
|
status: "pending",
|
|
paymentUrl,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
})
|
|
.returning({
|
|
id: paymentOrder.id,
|
|
});
|
|
|
|
return NextResponse.json({
|
|
orderId: created.id,
|
|
outTradeNo,
|
|
paymentUrl,
|
|
amount,
|
|
currency: selectedPackage.currency,
|
|
points: selectedPackage.points,
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
return NextResponse.json(
|
|
{
|
|
error: "请求参数错误",
|
|
details: error.errors,
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
console.error("[DuluPay Create] Error:", error);
|
|
return NextResponse.json({ error: "创建支付订单失败" }, { status: 500 });
|
|
}
|
|
}
|