Merge GitHub upstream into Gitea primary
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
applyAccountProfileConcurrencyGuards,
|
||||
resolveAccountBirthTimeApplicationPatch,
|
||||
} from "@/lib/account-profile-patch";
|
||||
import { createAdminSupabaseClient, isAdminEmail } from "@/lib/supabase/admin";
|
||||
import { createAdminSupabaseClient, isAdminUser } from "@/lib/supabase/admin";
|
||||
import {
|
||||
isSupabaseConfigurationError,
|
||||
} from "@/lib/supabase/config";
|
||||
@@ -109,11 +109,14 @@ export async function GET() {
|
||||
profile,
|
||||
Array.isArray(rectificationCaseRows) ? rectificationCaseRows : [],
|
||||
);
|
||||
const isAdmin = await isAdminUser(user);
|
||||
const adminUrl = isAdmin ? "/admin/codes" : null;
|
||||
|
||||
return NextResponse.json({
|
||||
user: { id: user.id, email: user.email ?? null },
|
||||
credits: profile.credits,
|
||||
isAdmin: isAdminEmail(user.email),
|
||||
isAdmin,
|
||||
adminUrl,
|
||||
rectificationPriceCredits,
|
||||
hasConfirmedBirthTime: profile.birth_time_status === "confirmed"
|
||||
&& typeof profile.active_birth_time === "string",
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import crypto from "node:crypto";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { requireAdminSession } from "@/lib/admin/auth";
|
||||
import { isPostgresError, queryAdminRows } from "@/lib/admin/database";
|
||||
import { adminErrorResponse } from "@/lib/admin/http";
|
||||
import { suggestedEpayUrls } from "@/lib/epay/config";
|
||||
import { encryptEpayKey } from "@/lib/epay/encryption";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const httpUrl = z.string().trim().min(1).max(2048).url().refine((value) => /^https?:\/\//i.test(value), "必须使用 HTTP(S)");
|
||||
const settingsSchema = z.object({
|
||||
gatewayUrl: httpUrl,
|
||||
pid: z.string().trim().min(1).max(200),
|
||||
notifyUrl: httpUrl,
|
||||
returnUrl: httpUrl,
|
||||
siteName: z.string().trim().min(1).max(100),
|
||||
chatEnabled: z.boolean(),
|
||||
newKey: z.string().min(1).max(1000).optional(),
|
||||
}).strict();
|
||||
|
||||
type SettingsRow = {
|
||||
gateway_url: string;
|
||||
pid: string;
|
||||
encrypted_key: string;
|
||||
notify_url: string;
|
||||
return_url: string;
|
||||
site_name: string;
|
||||
chat_enabled: boolean;
|
||||
updated_at?: Date;
|
||||
};
|
||||
|
||||
function publicSettings(row: SettingsRow, source: "database" | "environment") {
|
||||
return {
|
||||
gatewayUrl: row.gateway_url,
|
||||
pid: row.pid,
|
||||
notifyUrl: row.notify_url,
|
||||
returnUrl: row.return_url,
|
||||
siteName: row.site_name,
|
||||
chatEnabled: row.chat_enabled,
|
||||
keyConfigured: Boolean(row.encrypted_key),
|
||||
complete: Boolean(row.gateway_url && row.pid && row.encrypted_key && row.notify_url && row.return_url && row.site_name),
|
||||
source,
|
||||
updatedAt: source === "database" ? row.updated_at?.toISOString() ?? null : null,
|
||||
};
|
||||
}
|
||||
|
||||
function environmentSettings() {
|
||||
const defaults = suggestedEpayUrls();
|
||||
const row: SettingsRow = {
|
||||
gateway_url: process.env.EPAY_GATEWAY_URL?.trim() || "",
|
||||
pid: process.env.EPAY_PID?.trim() || "",
|
||||
encrypted_key: process.env.EPAY_KEY?.trim() ? "configured" : "",
|
||||
notify_url: process.env.EPAY_NOTIFY_URL?.trim() || defaults.notifyUrl,
|
||||
return_url: process.env.EPAY_RETURN_URL?.trim() || defaults.returnUrl,
|
||||
site_name: process.env.EPAY_SITE_NAME?.trim() || "Jyotisha",
|
||||
chat_enabled: ["true", "1"].includes(process.env.EPAY_CHAT_ENABLED?.trim().toLowerCase() || ""),
|
||||
};
|
||||
return publicSettings(row, "environment");
|
||||
}
|
||||
|
||||
async function databaseRow() {
|
||||
try {
|
||||
const rows = await queryAdminRows<SettingsRow>(`
|
||||
select gateway_url, pid, encrypted_key, notify_url, return_url, site_name, chat_enabled, updated_at
|
||||
from public.epay_settings
|
||||
where id = true
|
||||
limit 1
|
||||
`);
|
||||
return rows[0] ?? null;
|
||||
} catch (error) {
|
||||
if (isPostgresError(error) && error.code === "42P01") return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requireAdminSession("read");
|
||||
const row = await databaseRow();
|
||||
if (row) return NextResponse.json(publicSettings(row, "database"));
|
||||
const settings = environmentSettings();
|
||||
return NextResponse.json(settings.complete || settings.keyConfigured
|
||||
? settings
|
||||
: { ...settings, source: "unconfigured" });
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: Request) {
|
||||
try {
|
||||
const session = await requireAdminSession("write");
|
||||
const parsed = settingsSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return NextResponse.json({ error: "易支付配置参数不正确" }, { status: 400 });
|
||||
|
||||
const existing = await databaseRow();
|
||||
if (!existing && !parsed.data.newKey) {
|
||||
return NextResponse.json({ error: "首次保存数据库配置时必须输入新的商户密钥" }, { status: 400 });
|
||||
}
|
||||
const encryptedKey = parsed.data.newKey
|
||||
? encryptEpayKey(parsed.data.newKey)
|
||||
: existing!.encrypted_key;
|
||||
try {
|
||||
const rows = await queryAdminRows<SettingsRow>(`
|
||||
select * from public.admin_save_epay_settings(
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||
)
|
||||
`, [
|
||||
session.user.id,
|
||||
session.user.email,
|
||||
session.role,
|
||||
crypto.randomUUID(),
|
||||
parsed.data.gatewayUrl.replace(/\/+$/, ""),
|
||||
parsed.data.pid,
|
||||
encryptedKey,
|
||||
parsed.data.notifyUrl,
|
||||
parsed.data.returnUrl,
|
||||
parsed.data.siteName,
|
||||
parsed.data.chatEnabled,
|
||||
Boolean(parsed.data.newKey),
|
||||
]);
|
||||
if (!rows[0]) return NextResponse.json({ error: "保存易支付配置失败" }, { status: 500 });
|
||||
return NextResponse.json(publicSettings(rows[0], "database"));
|
||||
} catch {
|
||||
return NextResponse.json({ error: "保存易支付配置失败" }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { AdminAuthorizationError, requireAdminSession } from "@/lib/admin/auth";
|
||||
import { adminErrorResponse } from "@/lib/admin/http";
|
||||
import { epaySubmitUrl, readEpayConfig } from "@/lib/epay/config";
|
||||
import { assertPublicGatewayUrl } from "@/lib/epay/gateway-policy";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
function reachableStatus(status: number) {
|
||||
return status >= 200 && status < 500;
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
await requireAdminSession("write");
|
||||
const config = await readEpayConfig();
|
||||
const submitUrl = epaySubmitUrl(config.gatewayUrl);
|
||||
await assertPublicGatewayUrl(submitUrl);
|
||||
const startedAt = performance.now();
|
||||
let response = await fetch(submitUrl, {
|
||||
method: "HEAD",
|
||||
redirect: "manual",
|
||||
signal: AbortSignal.timeout(8_000),
|
||||
});
|
||||
if (response.status === 405 || response.status === 501) {
|
||||
response = await fetch(submitUrl, {
|
||||
method: "GET",
|
||||
redirect: "manual",
|
||||
signal: AbortSignal.timeout(8_000),
|
||||
});
|
||||
}
|
||||
const available = reachableStatus(response.status);
|
||||
return NextResponse.json({
|
||||
available,
|
||||
message: available ? "当前已保存的易支付配置可访问" : "当前已保存的易支付配置暂不可用",
|
||||
latencyMs: Math.round(performance.now() - startedAt),
|
||||
status: response.status,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof AdminAuthorizationError) return adminErrorResponse(error);
|
||||
return NextResponse.json({
|
||||
available: false,
|
||||
message: "当前已保存的易支付配置暂不可用",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { requireAdminSession } from "@/lib/admin/auth";
|
||||
import { queryAdminRows } from "@/lib/admin/database";
|
||||
import { adminErrorResponse } from "@/lib/admin/http";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().trim().min(1).max(80),
|
||||
description: z.string().trim().max(500),
|
||||
priceCents: z.number().int().positive().max(100_000_000),
|
||||
credits: z.number().int().positive().max(10_000_000),
|
||||
sortOrder: z.number().int().min(-100_000).max(100_000),
|
||||
enabled: z.boolean(),
|
||||
}).strict();
|
||||
const updateSchema = schema.extend({ id: z.string().uuid() });
|
||||
const idSchema = z.object({ id: z.string().uuid() }).strict();
|
||||
|
||||
type PackageRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price_cents: number;
|
||||
credits: number;
|
||||
sort_order: number;
|
||||
enabled: boolean;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
};
|
||||
|
||||
function output(row: PackageRow) {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
priceCents: row.price_cents,
|
||||
credits: row.credits,
|
||||
sortOrder: row.sort_order,
|
||||
enabled: row.enabled,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
updatedAt: row.updated_at.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requireAdminSession("read");
|
||||
const rows = await queryAdminRows<PackageRow>(`
|
||||
select id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
|
||||
from public.payment_packages
|
||||
order by sort_order, created_at
|
||||
`);
|
||||
return NextResponse.json({ packages: rows.map(output) });
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const auth = await requireAdminSession("write");
|
||||
const parsed = schema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
|
||||
const p = parsed.data;
|
||||
const rows = await queryAdminRows<PackageRow>(`
|
||||
insert into public.payment_packages
|
||||
(name, description, price_cents, credits, sort_order, enabled, created_by)
|
||||
values ($1, $2, $3, $4, $5, $6, $7)
|
||||
returning id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
|
||||
`, [p.name, p.description, p.priceCents, p.credits, p.sortOrder, p.enabled, auth.user.id]);
|
||||
return NextResponse.json({ package: output(rows[0]) }, { status: 201 });
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
try {
|
||||
await requireAdminSession("write");
|
||||
const parsed = updateSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
|
||||
const p = parsed.data;
|
||||
const rows = await queryAdminRows<PackageRow>(`
|
||||
update public.payment_packages
|
||||
set name = $2, description = $3, price_cents = $4, credits = $5,
|
||||
sort_order = $6, enabled = $7, updated_at = clock_timestamp()
|
||||
where id = $1
|
||||
returning id, name, description, price_cents, credits, sort_order, enabled, created_at, updated_at
|
||||
`, [p.id, p.name, p.description, p.priceCents, p.credits, p.sortOrder, p.enabled]);
|
||||
if (!rows[0]) return NextResponse.json({ error: "套餐不存在" }, { status: 404 });
|
||||
return NextResponse.json({ package: output(rows[0]) });
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
await requireAdminSession("write");
|
||||
const parsed = idSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return NextResponse.json({ error: "套餐参数不正确" }, { status: 400 });
|
||||
const rows = await queryAdminRows<{ id: string }>(`
|
||||
update public.payment_packages
|
||||
set enabled = false, updated_at = clock_timestamp()
|
||||
where id = $1
|
||||
returning id
|
||||
`, [parsed.data.id]);
|
||||
if (!rows[0]) return NextResponse.json({ error: "套餐不存在" }, { status: 404 });
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
return adminErrorResponse(error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { requireAdminSession } from "@/lib/admin/auth";
|
||||
import { queryAdminRows } from "@/lib/admin/database";
|
||||
import { adminErrorResponse } from "@/lib/admin/http";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const querySchema = z.object({
|
||||
status: z.enum(["pending", "paid", "failed", "expired"]).optional(),
|
||||
from: z.string().datetime({ offset: true }).optional(),
|
||||
to: z.string().datetime({ offset: true }).optional(),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(20),
|
||||
offset: z.coerce.number().int().min(0).default(0),
|
||||
});
|
||||
|
||||
type PaymentOrderRow = {
|
||||
order_no: string;
|
||||
user_email: string | null;
|
||||
package_name: string | null;
|
||||
money_cents: number;
|
||||
credits: number;
|
||||
status: string;
|
||||
epay_trade_no: string | null;
|
||||
created_at: Date;
|
||||
paid_at: Date | null;
|
||||
total_count: string;
|
||||
};
|
||||
|
||||
type PaymentStatsRow = {
|
||||
total_orders: string;
|
||||
paid_orders: string;
|
||||
pending_orders: string;
|
||||
failed_expired_orders: string;
|
||||
paid_amount_cents: string;
|
||||
granted_credits: string;
|
||||
};
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
await requireAdminSession("read");
|
||||
|
||||
const url = new URL(request.url);
|
||||
const parsed = querySchema.safeParse(Object.fromEntries(url.searchParams));
|
||||
if (!parsed.success) return NextResponse.json({ error: "查询参数不正确" }, { status: 400 });
|
||||
const { status, from, to, limit, offset } = parsed.data;
|
||||
if (from && to && new Date(from) > new Date(to)) return NextResponse.json({ error: "开始日期不能晚于结束日期" }, { status: 400 });
|
||||
|
||||
const values: unknown[] = [];
|
||||
const conditions: string[] = [];
|
||||
if (status) {
|
||||
values.push(status);
|
||||
conditions.push(`o.status = $${values.length}`);
|
||||
}
|
||||
if (from) {
|
||||
values.push(from);
|
||||
conditions.push(`o.created_at >= $${values.length}::timestamptz`);
|
||||
}
|
||||
if (to) {
|
||||
values.push(to);
|
||||
conditions.push(`o.created_at <= $${values.length}::timestamptz`);
|
||||
}
|
||||
const statsValues: unknown[] = [];
|
||||
const dateConditions: string[] = [];
|
||||
if (from) {
|
||||
statsValues.push(from);
|
||||
dateConditions.push(`o.created_at >= $${statsValues.length}::timestamptz`);
|
||||
}
|
||||
if (to) {
|
||||
statsValues.push(to);
|
||||
dateConditions.push(`o.created_at <= $${statsValues.length}::timestamptz`);
|
||||
}
|
||||
values.push(limit, offset);
|
||||
|
||||
const [rows, statsRows] = await Promise.all([
|
||||
queryAdminRows<PaymentOrderRow>(`
|
||||
select
|
||||
o.order_no, u.email as user_email, p.name as package_name,
|
||||
o.money_cents, o.credits, o.status, o.epay_trade_no,
|
||||
o.created_at, o.paid_at, count(*) over()::text as total_count
|
||||
from public.payment_orders o
|
||||
left join public.payment_packages p on p.id = o.package_id
|
||||
left join identity.users u on u.id = o.user_id
|
||||
${conditions.length ? `where ${conditions.join(" and ")}` : ""}
|
||||
order by o.created_at desc, o.order_no asc
|
||||
limit $${values.length - 1} offset $${values.length}
|
||||
`, values),
|
||||
queryAdminRows<PaymentStatsRow>(`
|
||||
select
|
||||
count(*)::text as total_orders,
|
||||
count(*) filter (where o.status = 'paid')::text as paid_orders,
|
||||
count(*) filter (where o.status = 'pending')::text as pending_orders,
|
||||
count(*) filter (where o.status in ('failed', 'expired'))::text as failed_expired_orders,
|
||||
coalesce(sum(o.money_cents) filter (where o.status = 'paid'), 0)::text as paid_amount_cents,
|
||||
coalesce(sum(o.credits) filter (where o.status = 'paid'), 0)::text as granted_credits
|
||||
from public.payment_orders o
|
||||
${dateConditions.length ? `where ${dateConditions.join(" and ")}` : ""}
|
||||
`, statsValues),
|
||||
]);
|
||||
|
||||
const orders = rows.map((row) => ({
|
||||
orderNo: row.order_no,
|
||||
userEmail: row.user_email,
|
||||
packageName: row.package_name,
|
||||
moneyCents: row.money_cents,
|
||||
credits: row.credits,
|
||||
status: row.status,
|
||||
epayTradeNo: row.epay_trade_no,
|
||||
createdAt: row.created_at.toISOString(),
|
||||
paidAt: row.paid_at?.toISOString() ?? null,
|
||||
}));
|
||||
const rawStats = statsRows[0];
|
||||
const stats = {
|
||||
totalOrders: Number(rawStats?.total_orders ?? 0),
|
||||
paidOrders: Number(rawStats?.paid_orders ?? 0),
|
||||
pendingOrders: Number(rawStats?.pending_orders ?? 0),
|
||||
failedExpiredOrders: Number(rawStats?.failed_expired_orders ?? 0),
|
||||
paidAmountCents: Number(rawStats?.paid_amount_cents ?? 0),
|
||||
grantedCredits: Number(rawStats?.granted_credits ?? 0),
|
||||
};
|
||||
const total = Number(rows[0]?.total_count ?? 0);
|
||||
return NextResponse.json({ orders, stats, pagination: { limit, offset, total, hasMore: offset + orders.length < total } });
|
||||
} catch (error) {
|
||||
const response = adminErrorResponse(error);
|
||||
if (response.status === 401 || response.status === 403) return response;
|
||||
return NextResponse.json({ error: "支付记录服务暂时不可用" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ async function dispatch(
|
||||
const services = getIdentityAuthServices();
|
||||
const handlers = createHostIsolatedAuthHandlers(config, {
|
||||
user: toNextJsHandler(services.user),
|
||||
admin: toNextJsHandler(services.admin),
|
||||
});
|
||||
return handlers[method](request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import crypto from "node:crypto";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import { epaySign } from "@/lib/epay/sign";
|
||||
import { readEpayAvailability } from "@/lib/epay/availability";
|
||||
import { epaySubmitUrl, readEpayConfig, EpayConfigurationError } from "@/lib/epay/config";
|
||||
import { assertPublicGatewayUrl } from "@/lib/epay/gateway-policy";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
const schema = z.object({ packageId: z.string().uuid() });
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const client = await createServerSupabaseClient();
|
||||
const { data: { user } } = await client.auth.getUser();
|
||||
if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 });
|
||||
const parsed = schema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) return NextResponse.json({ error: "请选择有效套餐" }, { status: 400 });
|
||||
|
||||
const availability = await readEpayAvailability();
|
||||
if (!availability.enabled) return NextResponse.json({ error: "在线支付暂未开放", code: "EPAY_DISABLED" }, { status: 403 });
|
||||
const config = await readEpayConfig();
|
||||
const submitUrl = epaySubmitUrl(config.gatewayUrl);
|
||||
await assertPublicGatewayUrl(submitUrl);
|
||||
|
||||
const admin = createAdminSupabaseClient();
|
||||
const { data: pack, error: packError } = await admin.from("payment_packages").select("id,name,price_cents,credits,enabled").eq("id", parsed.data.packageId).eq("enabled", true).maybeSingle();
|
||||
if (packError || !pack) return NextResponse.json({ error: "套餐不存在或已下架" }, { status: 404 });
|
||||
const orderNo = `JY${Date.now().toString(36)}${crypto.randomBytes(10).toString("hex")}`;
|
||||
const { error: orderError } = await admin.from("payment_orders").insert({ order_no: orderNo, user_id: user.id, package_id: pack.id, money_cents: pack.price_cents, credits: pack.credits });
|
||||
if (orderError) return NextResponse.json({ error: "创建订单失败" }, { status: 500 });
|
||||
|
||||
const params = {
|
||||
money: (pack.price_cents / 100).toFixed(2),
|
||||
name: pack.name,
|
||||
notify_url: config.notifyUrl,
|
||||
out_trade_no: orderNo,
|
||||
pid: config.pid,
|
||||
return_url: config.returnUrl,
|
||||
sitename: config.siteName,
|
||||
type: "alipay",
|
||||
};
|
||||
const signedParams = { ...params, sign: epaySign(params, config.key), sign_type: "MD5" };
|
||||
const payUrl = new URL(submitUrl);
|
||||
for (const [name, value] of Object.entries(signedParams)) payUrl.searchParams.set(name, value);
|
||||
return NextResponse.json({ orderNo, payUrl: payUrl.toString(), qrCode: null });
|
||||
} catch (error) {
|
||||
if (error instanceof EpayConfigurationError) return NextResponse.json({ error: "易支付尚未配置", code: "EPAY_NOT_CONFIGURED" }, { status: 503 });
|
||||
return NextResponse.json({ error: "创建支付失败" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { readEpayConfig } from "@/lib/epay/config";
|
||||
import { createEpayNotifyHandler } from "@/lib/epay/notify-core";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const notify = createEpayNotifyHandler({
|
||||
readConfig: readEpayConfig,
|
||||
settle: async (args) => await createAdminSupabaseClient().rpc("settle_epay_order", args),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) { return notify(request); }
|
||||
export async function GET(request: Request) { return notify(request); }
|
||||
@@ -0,0 +1,5 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
export const runtime = "nodejs";
|
||||
export async function GET(request: Request) { const client = await createServerSupabaseClient(); const { data: { user } } = await client.auth.getUser(); if (!user) return NextResponse.json({ error: "请先登录" }, { status: 401 }); const orderNo = new URL(request.url).searchParams.get("orderNo"); if (!orderNo) return NextResponse.json({ error: "缺少订单号" }, { status: 400 }); const { data, error } = await createAdminSupabaseClient().from("payment_orders").select("order_no,status,credits,paid_at").eq("order_no", orderNo).eq("user_id", user.id).maybeSingle(); if (error) return NextResponse.json({ error: "暂时无法查询订单" }, { status: 500 }); if (!data) return NextResponse.json({ error: "订单不存在" }, { status: 404 }); return NextResponse.json({ orderNo: data.order_no, status: data.status, credits: data.credits, paidAt: data.paid_at }); }
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { readEpayAvailability } from "@/lib/epay/availability";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
export async function GET() {
|
||||
const availability = await readEpayAvailability();
|
||||
if (!availability.enabled) return NextResponse.json({ enabled: false, packages: [] });
|
||||
|
||||
const { data, error } = await createAdminSupabaseClient()
|
||||
.from("payment_packages")
|
||||
.select("id,name,description,price_cents,credits,sort_order")
|
||||
.eq("enabled", true)
|
||||
.order("sort_order")
|
||||
.order("created_at");
|
||||
if (error) return NextResponse.json({ enabled: false, packages: [] });
|
||||
return NextResponse.json({
|
||||
enabled: true,
|
||||
packages: (data || []).map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
priceCents: item.price_cents,
|
||||
credits: item.credits,
|
||||
})),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user