"use server"; import { requireAuth } from "@/lib/require-auth"; import { prisma } from "@/lib/prisma"; import { getErrorMessage } from "@/lib/errors"; import { buildUnavailableMessage, formatAvailabilityDateTime, getPlanAvailability, } from "@/services/plan-availability"; import { ensurePlanTrafficPoolCapacity, getPlanTrafficPoolState, } from "@/services/plan-traffic-pool"; import { getPlanPurchasePrice, roundMoney } from "@/services/commerce"; async function assertNoPendingOrder(userId: string) { const pendingOrder = await prisma.order.findFirst({ where: { userId, status: "PENDING" }, select: { id: true }, orderBy: { createdAt: "desc" }, }); if (pendingOrder) { throw new Error("你还有一笔订单正在等待支付,请先完成或取消后再继续购买"); } } function getRenewalOrderPrice( plan: { durationDays: number; renewalPrice: unknown; renewalPricingMode: string; renewalDurationDays: number | null; renewalMinDays: number | null; renewalMaxDays: number | null; }, requestedDays?: number, ) { const unitPrice = Number(plan.renewalPrice ?? 0); if (!Number.isFinite(unitPrice) || unitPrice <= 0) { throw new Error("这款套餐暂时不支持续费"); } const mode = plan.renewalPricingMode === "PER_DAY" ? "PER_DAY" : "FIXED_DURATION"; if (mode === "PER_DAY") { const minDays = plan.renewalMinDays ?? 1; const maxDays = plan.renewalMaxDays ?? plan.durationDays; const durationDays = requestedDays ?? minDays; if (!Number.isInteger(durationDays) || durationDays < minDays || durationDays > maxDays) { throw new Error(`续费天数范围: ${minDays}-${maxDays} 天`); } return { durationDays, amount: roundMoney(unitPrice * durationDays), }; } const unitDays = plan.renewalDurationDays ?? plan.durationDays; const minDays = plan.renewalMinDays ?? unitDays; const maxDays = plan.renewalMaxDays ?? unitDays; const durationDays = requestedDays ?? unitDays; if (!Number.isInteger(durationDays) || durationDays < minDays || durationDays > maxDays) { throw new Error(`续费天数范围: ${minDays}-${maxDays} 天`); } if (durationDays % unitDays !== 0) { throw new Error(`续费天数必须是 ${unitDays} 天的整数倍`); } return { durationDays, amount: roundMoney(unitPrice * (durationDays / unitDays)), }; } function getTrafficTopupOrderPrice( plan: { topupPricingMode: string; topupPricePerGb: unknown; topupFixedPrice: unknown; minTopupGb: number | null; maxTopupGb: number | null; pricePerGb: unknown; }, trafficGb: number, ) { const minTopupGb = plan.minTopupGb ?? 1; const maxTopupGb = plan.maxTopupGb ?? null; if (trafficGb < minTopupGb) { throw new Error(`单次至少增加 ${minTopupGb} GB`); } if (maxTopupGb != null && trafficGb > maxTopupGb) { throw new Error(`单次最多增加 ${maxTopupGb} GB`); } if (plan.topupPricingMode === "FIXED_AMOUNT") { const fixedAmount = Number(plan.topupFixedPrice ?? 0); if (!Number.isFinite(fixedAmount) || fixedAmount <= 0) { throw new Error("这款套餐暂时不能增加流量"); } return roundMoney(fixedAmount); } const pricePerGb = Number(plan.topupPricePerGb ?? plan.pricePerGb ?? 0); if (!Number.isFinite(pricePerGb) || pricePerGb <= 0) { throw new Error("这款套餐暂时不能增加流量"); } return roundMoney(pricePerGb * trafficGb); } export async function purchaseProxy( planId: string, trafficGb: number, selectedInboundId: string, ): Promise { const session = await requireAuth(); await assertNoPendingOrder(session.user.id); const plan = await prisma.subscriptionPlan.findUniqueOrThrow({ where: { id: planId }, include: { inboundOptions: { include: { inbound: { select: { id: true, isActive: true, serverId: true, }, }, }, }, }, }); if (plan.type !== "PROXY") throw new Error(`套餐类型不匹配:${plan.name} 是 ${plan.type},不能作为代理套餐购买`); if (!plan.isActive) throw new Error(`套餐已下架:${plan.name} 当前不可购买`); const price = getPlanPurchasePrice(plan, trafficGb); const poolState = await getPlanTrafficPoolState(plan.id); if (poolState.enabled && price.trafficGb != null) { await ensurePlanTrafficPoolCapacity(plan.id, price.trafficGb, { messagePrefix: "这款套餐额度暂时不足", }); } const availability = await getPlanAvailability(plan, { userId: session.user.id }); if (!availability.available) { throw new Error(buildUnavailableMessage(availability)); } const selectableInboundIds = plan.inboundOptions .filter( (item) => item.inbound.isActive && (!plan.nodeId || item.inbound.serverId === plan.nodeId), ) .map((item) => item.inboundId); let fallbackInboundId = ""; if (plan.inboundId && plan.nodeId) { const fallbackInbound = await prisma.nodeInbound.findFirst({ where: { id: plan.inboundId, serverId: plan.nodeId, isActive: true, }, select: { id: true }, }); fallbackInboundId = fallbackInbound?.id ?? ""; } const selectable = selectableInboundIds.length > 0 ? selectableInboundIds : [fallbackInboundId]; if (!selectedInboundId || !selectable.filter(Boolean).includes(selectedInboundId)) { throw new Error("请选择有效的线路入口"); } if (!selectable[0]) { throw new Error("这款套餐的线路入口正在整理中,暂时不能购买"); } const order = await prisma.order.create({ data: { userId: session.user.id, planId, kind: "NEW_PURCHASE", amount: price.amount, subtotalAmount: price.amount, discountAmount: 0, selectedInboundId, status: "PENDING", items: { create: { planId, selectedInboundId, trafficGb: price.trafficGb, unitAmount: price.unitAmount, amount: price.amount, }, }, }, }); return order.id; } export async function purchaseStreaming(planId: string): Promise { const session = await requireAuth(); await assertNoPendingOrder(session.user.id); const plan = await prisma.subscriptionPlan.findUniqueOrThrow({ where: { id: planId }, }); if (plan.type !== "STREAMING") throw new Error(`套餐类型不匹配:${plan.name} 是 ${plan.type},不能作为流媒体套餐购买`); if (!plan.isActive) throw new Error(`套餐已下架:${plan.name} 当前不可购买`); const availability = await getPlanAvailability(plan, { userId: session.user.id }); if (!availability.available) { throw new Error(buildUnavailableMessage(availability)); } const price = getPlanPurchasePrice(plan); const order = await prisma.order.create({ data: { userId: session.user.id, planId, kind: "NEW_PURCHASE", amount: price.amount, subtotalAmount: price.amount, discountAmount: 0, status: "PENDING", items: { create: { planId, trafficGb: null, unitAmount: price.unitAmount, amount: price.amount, }, }, }, }); return order.id; } export type PurchaseRenewalResult = | { ok: true; orderId: string } | { ok: false; error: string }; export async function purchaseRenewal( subscriptionId: string, renewalDays?: number, ): Promise { try { const session = await requireAuth(); await assertNoPendingOrder(session.user.id); const subscription = await prisma.userSubscription.findFirst({ where: { id: subscriptionId, userId: session.user.id }, include: { plan: true, }, }); if (!subscription) throw new Error("订阅不存在"); if (subscription.status !== "ACTIVE" || subscription.endDate <= new Date()) { throw new Error("仅支持对活跃订阅续费"); } if (!subscription.plan.allowRenewal) { throw new Error("这款套餐暂时不支持续费"); } const price = getRenewalOrderPrice(subscription.plan, renewalDays); const order = await prisma.order.create({ data: { userId: session.user.id, planId: subscription.planId, kind: "RENEWAL", targetSubscriptionId: subscription.id, amount: price.amount, subtotalAmount: price.amount, discountAmount: 0, durationDays: price.durationDays, status: "PENDING", }, }); return { ok: true, orderId: order.id }; } catch (error) { return { ok: false, error: getErrorMessage(error, "创建续费订单失败") }; } } export async function purchaseTrafficTopup( subscriptionId: string, trafficGb: number, ): Promise { const session = await requireAuth(); await assertNoPendingOrder(session.user.id); if (!Number.isFinite(trafficGb) || trafficGb <= 0 || !Number.isInteger(trafficGb)) { throw new Error("增流量必须是正整数 GB"); } const subscription = await prisma.userSubscription.findFirst({ where: { id: subscriptionId, userId: session.user.id }, include: { plan: true, }, }); if (!subscription) throw new Error("订阅不存在"); if (subscription.plan.type !== "PROXY") { throw new Error("仅代理订阅支持增流量"); } if (subscription.status !== "ACTIVE" || subscription.endDate <= new Date()) { throw new Error("增流量仅在当前套餐有效期内生效"); } if (!subscription.plan.allowTrafficTopup) { throw new Error("这款套餐暂时不支持增加流量"); } const amount = getTrafficTopupOrderPrice(subscription.plan, trafficGb); const poolState = await getPlanTrafficPoolState(subscription.planId); if (poolState.enabled) { const remainingGb = Math.max(0, Math.floor(poolState.remainingGb)); if (trafficGb > remainingGb) { throw new Error(`剩余总流量不足,当前最多可增 ${remainingGb} GB`); } } const order = await prisma.order.create({ data: { userId: session.user.id, planId: subscription.planId, kind: "TRAFFIC_TOPUP", targetSubscriptionId: subscription.id, amount, subtotalAmount: amount, discountAmount: 0, trafficGb, status: "PENDING", }, }); return order.id; } export async function queryPlanNextAvailability(planId: string): Promise<{ available: boolean; message: string; nextAvailableAt: string | null; }> { const session = await requireAuth(); const plan = await prisma.subscriptionPlan.findUniqueOrThrow({ where: { id: planId }, select: { id: true, type: true, totalLimit: true, perUserLimit: true, streamingServiceId: true, isActive: true, }, }); if (!plan.isActive) { return { available: false, message: "这款套餐暂时不可购买", nextAvailableAt: null, }; } const availability = await getPlanAvailability(plan, { userId: session.user.id }); return { available: availability.available, message: availability.available ? "这款套餐现在可以购买" : buildUnavailableMessage(availability), nextAvailableAt: availability.nextAvailableAt ? formatAvailabilityDateTime(availability.nextAvailableAt) : null, }; }