From 1bb75b2baa8c7c935ba36b742ff020f1125c08a8 Mon Sep 17 00:00:00 2001 From: erio Date: Fri, 3 Apr 2026 20:36:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=97=A7=E8=AE=A2=E5=8D=95=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E9=80=80=E6=AC=BE=20+=20=E7=A7=BB=E9=99=A4=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=80=80=E6=AC=BE=20+=20=E6=94=B9=E4=B8=BA=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E6=89=A3=E9=99=A4=E4=BD=99=E9=A2=9D/=E8=AE=A2?= =?UTF-8?q?=E9=98=85=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - providerInstanceId 为 null 的旧订单默认允许退款申请 - 移除 AUTO_REFUND_ENABLED 自动退款功能 - 改为 DEFAULT_DEDUCT_BALANCE:控制退款对话框默认勾选状态 --- src/__tests__/app/api/orders-my-route.test.ts | 4 ++-- src/app/admin/orders/page.tsx | 13 ++++++++++++ src/app/admin/payment-config/page.tsx | 20 ++++++++++--------- src/app/api/admin/config/route.ts | 2 +- src/app/api/orders/my/route.ts | 2 +- src/components/admin/RefundDialog.tsx | 4 +++- src/lib/order/service.ts | 11 ---------- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/__tests__/app/api/orders-my-route.test.ts b/src/__tests__/app/api/orders-my-route.test.ts index 98ddeb8..3219bca 100644 --- a/src/__tests__/app/api/orders-my-route.test.ts +++ b/src/__tests__/app/api/orders-my-route.test.ts @@ -164,7 +164,7 @@ describe('GET /api/orders/my - canRefundRequest', () => { expect(data.orders[0].canRefundRequest).toBe(false); }); - it('canRefundRequest = false when providerInstanceId is null', async () => { + it('canRefundRequest = true when providerInstanceId is null (legacy orders)', async () => { mockOrderFindMany.mockResolvedValue([ { id: 'order-005', @@ -184,7 +184,7 @@ describe('GET /api/orders/my - canRefundRequest', () => { const res = await GET(createRequest()); const data = await res.json(); - expect(data.orders[0].canRefundRequest).toBe(false); + expect(data.orders[0].canRefundRequest).toBe(true); }); it('canRefundRequest = false when instance not found in DB', async () => { diff --git a/src/app/admin/orders/page.tsx b/src/app/admin/orders/page.tsx index a35d959..4ddf33d 100644 --- a/src/app/admin/orders/page.tsx +++ b/src/app/admin/orders/page.tsx @@ -162,6 +162,7 @@ function AdminContent() { const [refundUserBalance, setRefundUserBalance] = useState(undefined); const [refundSubRemainingDays, setRefundSubRemainingDays] = useState(undefined); const [refundWarning, setRefundWarning] = useState(undefined); + const [defaultDeductBalance, setDefaultDeductBalance] = useState(true); const [refundRequireForce, setRefundRequireForce] = useState(false); const fetchOrders = useCallback(async () => { @@ -196,6 +197,17 @@ function AdminContent() { fetchOrders(); }, [fetchOrders]); + useEffect(() => { + if (!token) return; + fetch(`/api/admin/config?token=${token}`) + .then((r) => (r.ok ? r.json() : null)) + .then((data) => { + const val = data?.configs?.find((c: { key: string }) => c.key === 'DEFAULT_DEDUCT_BALANCE'); + if (val) setDefaultDeductBalance(val.value === 'true'); + }) + .catch(() => {}); + }, [token]); + if (!token) { return (
@@ -482,6 +494,7 @@ function AdminContent() { userBalance={refundUserBalance} subscriptionDays={refundOrder.subscriptionDays ?? undefined} subscriptionRemainingDays={refundSubRemainingDays} + defaultDeductBalance={defaultDeductBalance} warning={refundWarning} requireForce={refundRequireForce} onConfirm={handleConfirmRefund} diff --git a/src/app/admin/payment-config/page.tsx b/src/app/admin/payment-config/page.tsx index 964ba79..461b0e2 100644 --- a/src/app/admin/payment-config/page.tsx +++ b/src/app/admin/payment-config/page.tsx @@ -69,8 +69,8 @@ function getTexts(locale: Locale) { allChannels: 'All Channels', sub2apiAdminApiKey: 'Sub2API Admin API Key', sub2apiAdminApiKeyHint: 'Leave empty to use environment variable', - autoRefund: 'Auto Refund', - autoRefundHint: 'When enabled, user refund requests are executed immediately without admin approval', + defaultDeductBalance: 'Default Deduct Balance', + defaultDeductBalanceHint: 'When enabled, refund approval defaults to deducting balance/subscription', } : { missingToken: '缺少管理员凭证', @@ -132,8 +132,8 @@ function getTexts(locale: Locale) { allChannels: '全部渠道', sub2apiAdminApiKey: 'Sub2API Admin API Key', sub2apiAdminApiKeyHint: '留空则使用环境变量', - autoRefund: '自动退款', - autoRefundHint: '开启后,用户提交退款申请会立即执行退款,无需管理员审核', + defaultDeductBalance: '默认扣除余额/订阅', + defaultDeductBalanceHint: '开启后,退款通过时默认扣除余额/订阅;关闭时默认不扣除', }; } @@ -259,7 +259,7 @@ function PaymentConfigContent() { const [rcSaving, setRcSaving] = useState(false); const [rcLoadBalanceStrategy, setRcLoadBalanceStrategy] = useState('round-robin'); const [rcSub2apiKey, setRcSub2apiKey] = useState(''); - const [rcAutoRefundEnabled, setRcAutoRefundEnabled] = useState(false); + const [rcAutoRefundEnabled, setRcAutoRefundEnabled] = useState(true); // Override env const [rcOverrideEnv, setRcOverrideEnv] = useState(false); @@ -327,7 +327,7 @@ function PaymentConfigContent() { if (c.key === 'ORDER_TIMEOUT_MINUTES') setRcOrderTimeout(c.value); if (c.key === 'LOAD_BALANCE_STRATEGY') setRcLoadBalanceStrategy(c.value || 'round-robin'); if (c.key === 'SUB2API_ADMIN_API_KEY') setRcSub2apiKey(/\*{4,}/.test(c.value) ? '' : c.value); - if (c.key === 'AUTO_REFUND_ENABLED') setRcAutoRefundEnabled(c.value === 'true'); + if (c.key === 'DEFAULT_DEDUCT_BALANCE') setRcAutoRefundEnabled(c.value === 'true'); } setRcOverrideEnv(hasOverride); setRcOverrideSaved(hasOverride); @@ -577,7 +577,7 @@ function PaymentConfigContent() { label: '余额充值禁用', }, { - key: 'AUTO_REFUND_ENABLED', + key: 'DEFAULT_DEDUCT_BALANCE', value: rcAutoRefundEnabled ? 'true' : 'false', group: 'payment', label: '自动退款开关', @@ -775,9 +775,11 @@ function PaymentConfigContent() {
setRcAutoRefundEnabled(!rcAutoRefundEnabled)} /> - {t.autoRefund} + {t.defaultDeductBalance}
- {t.autoRefundHint} + + {t.defaultDeductBalanceHint} + {/* Cancel rate limit */} diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts index a1925c8..2e1f791 100644 --- a/src/app/api/admin/config/route.ts +++ b/src/app/api/admin/config/route.ts @@ -94,7 +94,7 @@ const ALLOWED_CONFIG_KEYS = new Set([ 'ENABLED_PROVIDERS', 'SUB2API_ADMIN_API_KEY', 'OVERRIDE_ENV_ENABLED', - 'AUTO_REFUND_ENABLED', + 'DEFAULT_DEDUCT_BALANCE', ]); export async function PUT(request: NextRequest) { diff --git a/src/app/api/orders/my/route.ts b/src/app/api/orders/my/route.ts index 3ca5717..6c5d4b1 100644 --- a/src/app/api/orders/my/route.ts +++ b/src/app/api/orders/my/route.ts @@ -79,7 +79,7 @@ export async function GET(request: NextRequest) { const derived = deriveOrderState(item); const instanceRefundEnabled = item.providerInstanceId ? (refundEnabledMap.get(item.providerInstanceId) ?? false) - : false; + : true; return { id: item.id, amount: Number(item.amount), diff --git a/src/components/admin/RefundDialog.tsx b/src/components/admin/RefundDialog.tsx index 5e779a3..2dcfcea 100644 --- a/src/components/admin/RefundDialog.tsx +++ b/src/components/admin/RefundDialog.tsx @@ -11,6 +11,7 @@ interface RefundDialogProps { subscriptionDays?: number; subscriptionRemainingDays?: number; requestedAmount?: number | null; + defaultDeductBalance?: boolean; onConfirm: (reason: string, force: boolean, deductBalance: boolean, amount?: number) => Promise; onCancel: () => void; warning?: string; @@ -27,6 +28,7 @@ export default function RefundDialog({ subscriptionDays, subscriptionRemainingDays, requestedAmount, + defaultDeductBalance = true, onConfirm, onCancel, warning, @@ -37,7 +39,7 @@ export default function RefundDialog({ const [reason, setReason] = useState(''); const [refundAmount, setRefundAmount] = useState((requestedAmount ?? amount).toFixed(2)); const [force, setForce] = useState(false); - const [deductBalance, setDeductBalance] = useState(true); + const [deductBalance, setDeductBalance] = useState(defaultDeductBalance); const [loading, setLoading] = useState(false); const currency = '¥'; diff --git a/src/lib/order/service.ts b/src/lib/order/service.ts index a7e8b20..bffff84 100644 --- a/src/lib/order/service.ts +++ b/src/lib/order/service.ts @@ -1173,7 +1173,6 @@ export async function requestRefund(input: RefundRequestInput): Promise<{ succes ); } - const autoRefundEnabled = (await getSystemConfig('AUTO_REFUND_ENABLED')) === 'true'; const normalizedReason = input.reason?.trim() || null; const updated = await prisma.order.updateMany({ @@ -1203,21 +1202,11 @@ export async function requestRefund(input: RefundRequestInput): Promise<{ succes amount: refundAmount, reason: normalizedReason, requestedBy: input.userId, - autoRefundEnabled, }), operator: `user:${input.userId}`, }, }); - if (autoRefundEnabled) { - return processRefund({ - orderId: input.orderId, - amount: refundAmount, - reason: normalizedReason || undefined, - locale, - }); - } - return { success: true }; }