'use client'; import { useState } from 'react'; import { PAYMENT_TYPE_META } from '@/lib/pay-utils'; export interface MethodLimitInfo { available: boolean; remaining: number | null; /** 单笔限额,0 = 使用全局 maxAmount */ singleMax?: number; } interface PaymentFormProps { userId: number; userName?: string; userBalance?: number; enabledPaymentTypes: string[]; methodLimits?: Record; minAmount: number; maxAmount: number; onSubmit: (amount: number, paymentType: string) => Promise; loading?: boolean; dark?: boolean; } const QUICK_AMOUNTS = [10, 20, 50, 100, 200, 500]; const AMOUNT_TEXT_PATTERN = /^\d*(\.\d{0,2})?$/; function hasValidCentPrecision(num: number): boolean { return Math.abs(Math.round(num * 100) - num * 100) < 1e-8; } export default function PaymentForm({ userId, userName, userBalance, enabledPaymentTypes, methodLimits, minAmount, maxAmount, onSubmit, loading, dark = false, }: PaymentFormProps) { const [amount, setAmount] = useState(''); const [paymentType, setPaymentType] = useState(enabledPaymentTypes[0] || 'alipay'); const [customAmount, setCustomAmount] = useState(''); const handleQuickAmount = (val: number) => { setAmount(val); setCustomAmount(String(val)); }; const handleCustomAmountChange = (val: string) => { if (!AMOUNT_TEXT_PATTERN.test(val)) { return; } setCustomAmount(val); if (val === '') { setAmount(''); return; } const num = parseFloat(val); if (!isNaN(num) && num > 0 && hasValidCentPrecision(num)) { setAmount(num); } else { setAmount(''); } }; const selectedAmount = amount || 0; const isMethodAvailable = !methodLimits || (methodLimits[paymentType]?.available !== false); const methodSingleMax = methodLimits?.[paymentType]?.singleMax; const effectiveMax = (methodSingleMax !== undefined && methodSingleMax > 0) ? methodSingleMax : maxAmount; const isValid = selectedAmount >= minAmount && selectedAmount <= effectiveMax && hasValidCentPrecision(selectedAmount) && isMethodAvailable; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!isValid || loading) return; await onSubmit(selectedAmount, paymentType); }; const renderPaymentIcon = (type: string) => { if (type === 'alipay') { return ( ); } if (type === 'wxpay') { return ( ); } if (type === 'stripe') { return ( ); } return null; }; return (
{/* User Info */}
充值账户
{userName || `用户 #${userId}`}
{userBalance !== undefined && (
当前余额: {userBalance.toFixed(2)}
)}
{/* Quick Amount Selection */}
{QUICK_AMOUNTS.filter((val) => val <= effectiveMax).map((val) => ( ))}
{/* Custom Amount */}
¥ handleCustomAmountChange(e.target.value)} placeholder={`${minAmount} - ${effectiveMax}`} className={[ 'w-full rounded-lg border py-3 pl-8 pr-4 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500', dark ? 'border-slate-700 bg-slate-900 text-slate-100' : 'border-gray-300 bg-white text-gray-900', ].join(' ')} />
{customAmount !== '' && !isValid && (() => { const num = parseFloat(customAmount); let msg = '金额需在范围内,且最多支持 2 位小数(精确到分)'; if (!isNaN(num)) { if (num < minAmount) msg = `单笔最低充值 ¥${minAmount}`; else if (num > effectiveMax) msg = `单笔最高充值 ¥${effectiveMax}`; } return (
{msg}
); })()} {/* Payment Type */}
{enabledPaymentTypes.map((type) => { const meta = PAYMENT_TYPE_META[type]; const isSelected = paymentType === type; const limitInfo = methodLimits?.[type]; const isUnavailable = limitInfo !== undefined && !limitInfo.available; return ( ); })}
{/* 当前选中渠道额度不足时的提示 */} {(() => { const limitInfo = methodLimits?.[paymentType]; if (!limitInfo || limitInfo.available) return null; return (

所选支付方式今日额度已满,请切换到其他支付方式

); })()}
{/* Submit */}
); }