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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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' : '完成';
|
||||
|
||||
@@ -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,
|
||||
|
||||
15
src/lib/order/status-url.ts
Normal file
15
src/lib/order/status-url.ts
Normal 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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user