From 1cb82d8fd7ea7ec4c2e440786d4586d94aeebd4e Mon Sep 17 00:00:00 2001 From: erio Date: Tue, 10 Mar 2026 14:29:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B6=88=E9=99=A4=20buildOrderStatusUrl?= =?UTF-8?q?=20=E9=87=8D=E5=A4=8D=E5=AE=9A=E4=B9=89=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=BD=AE=E8=AF=A2=E5=9B=9E=E8=B0=83=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 buildOrderStatusUrl 提取到 status-url.ts(客户端安全模块), 删除 OrderStatus/PaymentQRCode/result 三处重复定义 - OrderStatus.tsx 轮询 effect 使用 useRef 保存 onStateChange, 避免非 memoized 回调导致定时器不断重建 - result/page.tsx 增加 accessToken 最小长度校验, 避免无效参数触发无意义的 API 请求 --- src/app/pay/result/page.tsx | 12 ++---------- src/components/OrderStatus.tsx | 18 ++++++------------ src/components/PaymentQRCode.tsx | 10 +--------- src/lib/order/status-url.ts | 15 +++++++++++++++ 4 files changed, 24 insertions(+), 31 deletions(-) create mode 100644 src/lib/order/status-url.ts diff --git a/src/app/pay/result/page.tsx b/src/app/pay/result/page.tsx index 909c1df..a573dc4 100644 --- a/src/app/pay/result/page.tsx +++ b/src/app/pay/result/page.tsx @@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation'; import { useEffect, useState, Suspense } from 'react'; import { applyLocaleToSearchParams, pickLocaleText, resolveLocale, type Locale } from '@/lib/locale'; import type { PublicOrderStatusSnapshot } from '@/lib/order/status'; +import { buildOrderStatusUrl } from '@/lib/order/status-url'; type WindowWithAlipayBridge = Window & { AlipayJSBridge?: { @@ -54,15 +55,6 @@ function closeCurrentWindow() { }, 250); } -function buildOrderStatusUrl(orderId: string, accessToken?: string | null): string { - const query = new URLSearchParams(); - if (accessToken) { - query.set('access_token', accessToken); - } - const suffix = query.toString(); - return suffix ? `/api/orders/${orderId}?${suffix}` : `/api/orders/${orderId}`; -} - function getStatusConfig(order: PublicOrderStatusSnapshot | null, locale: Locale, hasAccessToken: boolean) { if (!order) { return locale === 'en' @@ -142,7 +134,7 @@ function ResultContent() { }, [isPopup]); useEffect(() => { - if (!outTradeNo || !accessToken) { + if (!outTradeNo || !accessToken || accessToken.length < 10) { setLoading(false); return; } diff --git a/src/components/OrderStatus.tsx b/src/components/OrderStatus.tsx index a361bc3..e2a5ab9 100644 --- a/src/components/OrderStatus.tsx +++ b/src/components/OrderStatus.tsx @@ -1,9 +1,10 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import type { Locale } from '@/lib/locale'; import type { PublicOrderStatusSnapshot } from '@/lib/order/status'; +import { buildOrderStatusUrl } from '@/lib/order/status-url'; interface OrderStatusProps { orderId: string; @@ -15,15 +16,6 @@ interface OrderStatusProps { locale?: Locale; } -function buildOrderStatusUrl(orderId: string, statusAccessToken?: string): string { - const query = new URLSearchParams(); - if (statusAccessToken) { - query.set('access_token', statusAccessToken); - } - const suffix = query.toString(); - return suffix ? `/api/orders/${orderId}?${suffix}` : `/api/orders/${orderId}`; -} - function getStatusConfig(order: PublicOrderStatusSnapshot, locale: Locale) { if (order.rechargeSuccess) { return locale === 'en' @@ -84,6 +76,8 @@ export default function OrderStatus({ locale = 'zh', }: OrderStatusProps) { const [currentOrder, setCurrentOrder] = useState(order); + const onStateChangeRef = useRef(onStateChange); + onStateChangeRef.current = onStateChange; useEffect(() => { setCurrentOrder(order); @@ -103,7 +97,7 @@ export default function OrderStatus({ const nextOrder = (await response.json()) as PublicOrderStatusSnapshot; if (cancelled) return; setCurrentOrder(nextOrder); - onStateChange?.(nextOrder); + onStateChangeRef.current?.(nextOrder); } catch { } }; @@ -117,7 +111,7 @@ export default function OrderStatus({ clearInterval(timer); clearTimeout(timeout); }; - }, [orderId, currentOrder.paymentSuccess, currentOrder.rechargeSuccess, onStateChange, statusAccessToken]); + }, [orderId, currentOrder.paymentSuccess, currentOrder.rechargeSuccess, statusAccessToken]); const config = getStatusConfig(currentOrder, locale); const doneLabel = locale === 'en' ? 'Done' : '完成'; diff --git a/src/components/PaymentQRCode.tsx b/src/components/PaymentQRCode.tsx index 6229e1d..28118e0 100644 --- a/src/components/PaymentQRCode.tsx +++ b/src/components/PaymentQRCode.tsx @@ -10,6 +10,7 @@ import { getPaymentIconSrc, getPaymentChannelLabel, } from '@/lib/pay-utils'; +import { buildOrderStatusUrl } from '@/lib/order/status-url'; import { TERMINAL_STATUSES } from '@/lib/constants'; interface PaymentQRCodeProps { @@ -36,15 +37,6 @@ function isVisibleOrderOutcome(data: PublicOrderStatusSnapshot): boolean { return data.paymentSuccess || TERMINAL_STATUSES.has(data.status); } -function buildOrderStatusUrl(orderId: string, statusAccessToken?: string): string { - const query = new URLSearchParams(); - if (statusAccessToken) { - query.set('access_token', statusAccessToken); - } - const suffix = query.toString(); - return suffix ? `/api/orders/${orderId}?${suffix}` : `/api/orders/${orderId}`; -} - export default function PaymentQRCode({ orderId, token, diff --git a/src/lib/order/status-url.ts b/src/lib/order/status-url.ts new file mode 100644 index 0000000..68b71ad --- /dev/null +++ b/src/lib/order/status-url.ts @@ -0,0 +1,15 @@ +/** + * Client-safe utility for building order status API URLs. + * This module must NOT import any server-only modules (config, fs, crypto, etc.). + */ + +const ACCESS_TOKEN_KEY = 'access_token'; + +export function buildOrderStatusUrl(orderId: string, accessToken?: string | null): string { + const query = new URLSearchParams(); + if (accessToken) { + query.set(ACCESS_TOKEN_KEY, accessToken); + } + const suffix = query.toString(); + return suffix ? `/api/orders/${orderId}?${suffix}` : `/api/orders/${orderId}`; +}