mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
fix: route wallet card redemption through api
This commit is contained in:
@@ -4,7 +4,7 @@ import { useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { CreditCard, Gift } from "lucide-react";
|
||||
import { createWalletRecharge, redeemWalletCard } from "@/actions/user/wallet";
|
||||
import { createWalletRecharge } from "@/actions/user/wallet";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -15,6 +15,32 @@ function money(value: number) {
|
||||
return `¥${value.toFixed(2)}`;
|
||||
}
|
||||
|
||||
type RedeemCardResult =
|
||||
| {
|
||||
type: "BALANCE";
|
||||
amount: number;
|
||||
balanceAfter: number;
|
||||
}
|
||||
| {
|
||||
type: "PLAN";
|
||||
planName: string;
|
||||
};
|
||||
|
||||
async function redeemWalletCardByApi(formData: FormData): Promise<RedeemCardResult> {
|
||||
const response = await fetch("/api/wallet/redeem-card", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ code: String(formData.get("code") ?? "") }),
|
||||
});
|
||||
const payload = await response.json().catch(() => null) as { error?: string } | RedeemCardResult | null;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(payload && "error" in payload && payload.error ? payload.error : "充值卡兑换失败");
|
||||
}
|
||||
|
||||
return payload as RedeemCardResult;
|
||||
}
|
||||
|
||||
export function WalletActions() {
|
||||
const router = useRouter();
|
||||
const [rechargePending, startRecharge] = useTransition();
|
||||
@@ -64,7 +90,7 @@ export function WalletActions() {
|
||||
const formData = new FormData(form);
|
||||
startRedeem(async () => {
|
||||
try {
|
||||
const result = await redeemWalletCard(formData);
|
||||
const result = await redeemWalletCardByApi(formData);
|
||||
form.reset();
|
||||
if (result.type === "BALANCE") {
|
||||
window.dispatchEvent(new CustomEvent(WALLET_BALANCE_UPDATED_EVENT, {
|
||||
|
||||
50
src/app/api/wallet/redeem-card/route.ts
Normal file
50
src/app/api/wallet/redeem-card/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { jsonError, jsonOk } from "@/lib/api-response";
|
||||
import { rateLimit } from "@/lib/rate-limit";
|
||||
import { getActiveSession } from "@/lib/require-auth";
|
||||
import { getActiveSubscriptionRiskRestriction } from "@/services/subscription-risk-review";
|
||||
import { redeemRechargeCard } from "@/services/wallet";
|
||||
|
||||
const redeemCardSchema = z.object({
|
||||
code: z.string().trim().min(4, "请输入充值卡卡密"),
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const session = await getActiveSession();
|
||||
if (!session) {
|
||||
return jsonError("未登录", { status: 401 });
|
||||
}
|
||||
|
||||
if (session.user.role !== "ADMIN") {
|
||||
const restriction = await getActiveSubscriptionRiskRestriction(session.user.id);
|
||||
if (restriction) {
|
||||
return jsonError("账户存在未处理的订阅风控限制,请先新建工单联系客服", { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
const { success, remaining } = await rateLimit(
|
||||
`ratelimit:redeem-card:${session.user.id}`,
|
||||
10,
|
||||
60,
|
||||
);
|
||||
if (!success) {
|
||||
return jsonError("兑换请求过于频繁,请稍后再试", {
|
||||
status: 429,
|
||||
headers: { "X-RateLimit-Remaining": String(remaining) },
|
||||
});
|
||||
}
|
||||
|
||||
const payload = redeemCardSchema.parse(await req.json());
|
||||
const result = await redeemRechargeCard(session.user.id, payload.code);
|
||||
|
||||
revalidatePath("/wallet");
|
||||
revalidatePath("/subscriptions");
|
||||
revalidatePath("/dashboard");
|
||||
|
||||
return jsonOk(result);
|
||||
} catch (error) {
|
||||
return jsonError(error, { fallback: "充值卡兑换失败" });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user