fix: 消除 buildOrderStatusUrl 重复定义,修复轮询回调引用稳定性

- 将 buildOrderStatusUrl 提取到 status-url.ts(客户端安全模块),
  删除 OrderStatus/PaymentQRCode/result 三处重复定义
- OrderStatus.tsx 轮询 effect 使用 useRef 保存 onStateChange,
  避免非 memoized 回调导致定时器不断重建
- result/page.tsx 增加 accessToken 最小长度校验,
  避免无效参数触发无意义的 API 请求
This commit is contained in:
erio
2026-03-10 14:29:22 +08:00
parent d6973256a7
commit 1cb82d8fd7
4 changed files with 24 additions and 31 deletions

View File

@@ -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;
}

View File

@@ -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' : '完成';

View File

@@ -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,

View File

@@ -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}`;
}