diff --git a/src/components/Subscription/SubscriptionContentNew.tsx b/src/components/Subscription/SubscriptionContentNew.tsx index f1d856eb..f92fb3a3 100644 --- a/src/components/Subscription/SubscriptionContentNew.tsx +++ b/src/components/Subscription/SubscriptionContentNew.tsx @@ -47,37 +47,37 @@ export default function SubscriptionContentNew() { const subscriptionEvents = useSubscriptionEvents({ currentSubscription: { plan: user?.subscription_plan || 'free', - status: user?.subscription_status || 'none', + status: user?.subscription_status || 'inactive', }, }); - const toast = useToast(); - const { isOpen: isPaymentModalOpen, onOpen: onPaymentModalOpen, onClose: onPaymentModalClose } = useDisclosure(); - - // State + const [selectedCycle, setSelectedCycle] = useState('yearly'); const [selectedPlan, setSelectedPlan] = useState(null); - const [selectedCycle, setSelectedCycle] = useState('yearly'); // 默认年付 - const [paymentOrder, setPaymentOrder] = useState(null); + const [priceInfo, setPriceInfo] = useState(null); const [loading, setLoading] = useState(false); - const [paymentCountdown, setPaymentCountdown] = useState(0); - const [checkingPayment, setCheckingPayment] = useState(false); - const [autoCheckInterval, setAutoCheckInterval] = useState(null); - const [forceUpdating, setForceUpdating] = useState(false); - const [openFaqIndex, setOpenFaqIndex] = useState(null); const [promoCode, setPromoCode] = useState(''); const [promoCodeApplied, setPromoCodeApplied] = useState(false); const [promoCodeError, setPromoCodeError] = useState(''); const [validatingPromo, setValidatingPromo] = useState(false); - const [priceInfo, setPriceInfo] = useState(null); + + const [paymentOrder, setPaymentOrder] = useState(null); + const [paymentCountdown, setPaymentCountdown] = useState(300); + const [autoCheckInterval, setAutoCheckInterval] = useState(null); + const [forceUpdating, setForceUpdating] = useState(false); + + const [openFaqIndex, setOpenFaqIndex] = useState(null); + + const { isOpen, onOpen, onClose } = useDisclosure(); + const toast = useToast(); // 倒计时更新 useEffect(() => { - let timer; + let timer: any; if (paymentCountdown > 0) { timer = setInterval(() => { setPaymentCountdown((prev) => { if (prev <= 1) { - handlePaymentExpired(); + handlePaymentExpire(); return 0; } return prev - 1; @@ -94,9 +94,7 @@ export default function SubscriptionContentNew() { }; }, []); - const handlePaymentExpired = () => { - setPaymentOrder(null); - setPaymentCountdown(0); + const handlePaymentExpire = () => { stopAutoPaymentCheck(); toast({ title: '支付二维码已过期', @@ -114,14 +112,14 @@ export default function SubscriptionContentNew() { } }; - const formatTime = (seconds) => { + const formatTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; }; // 计算价格 - const calculatePrice = async (plan, cycle, promoCodeValue = null) => { + const calculatePrice = async (plan: any, cycle: string, promoCodeValue: any = null) => { try { const validPromoCode = promoCodeValue && typeof promoCodeValue === 'string' && promoCodeValue.trim() ? promoCodeValue.trim() @@ -201,10 +199,11 @@ export default function SubscriptionContentNew() { } }; - const handleSubscribe = async (plan) => { + const handleSubscribe = async (plan: any) => { if (!user) { toast({ title: '请先登录', + description: '登录后即可订阅', status: 'warning', duration: 3000, isClosable: true, @@ -220,11 +219,11 @@ export default function SubscriptionContentNew() { setSelectedPlan(plan); await calculatePrice(plan, selectedCycle, promoCodeApplied ? promoCode : null); - onPaymentModalOpen(); + onOpen(); }; - const handleCreateOrder = async () => { - if (!selectedPlan) return; + const handleCreatePaymentOrder = async () => { + if (!selectedPlan || !user) return; setLoading(true); try { @@ -235,7 +234,6 @@ export default function SubscriptionContentNew() { paymentMethod: 'wechat_pay', amount: price, billingCycle: selectedCycle, - orderId: null, }); const response = await fetch('/api/payment/create-order', { @@ -245,7 +243,7 @@ export default function SubscriptionContentNew() { }, credentials: 'include', body: JSON.stringify({ - plan_name: selectedPlan.name, + plan_type: selectedPlan.name, billing_cycle: selectedCycle, promo_code: promoCodeApplied ? promoCode : null, }), @@ -255,12 +253,12 @@ export default function SubscriptionContentNew() { const data = await response.json(); if (data.success) { setPaymentOrder(data.data); - setPaymentCountdown(30 * 60); - startAutoPaymentCheck(data.data.id); + setPaymentCountdown(300); + startAutoPaymentCheck(data.data.order_id); toast({ - title: '订单创建成功', - description: '请使用微信扫描二维码完成支付', + title: '订单已创建', + description: '请使用微信扫码支付', status: 'success', duration: 3000, isClosable: true, @@ -269,9 +267,9 @@ export default function SubscriptionContentNew() { throw new Error(data.message || '创建订单失败'); } } else { - throw new Error('网络错误'); + throw new Error('网络请求失败'); } - } catch (error) { + } catch (error: any) { subscriptionEvents.trackPaymentFailed( { planName: selectedPlan.name, @@ -293,7 +291,7 @@ export default function SubscriptionContentNew() { } }; - const startAutoPaymentCheck = (orderId) => { + const startAutoPaymentCheck = (orderId: string) => { const checkInterval = setInterval(async () => { try { const response = await fetch(`/api/payment/order/${orderId}/status`, { @@ -302,7 +300,7 @@ export default function SubscriptionContentNew() { if (response.ok) { const data = await response.json(); - if (data.success && data.payment_success) { + if (data.success && data.data.status === 'paid') { clearInterval(checkInterval); setAutoCheckInterval(null); @@ -310,129 +308,65 @@ export default function SubscriptionContentNew() { planName: selectedPlan?.name, paymentMethod: 'wechat_pay', amount: paymentOrder?.amount, - billingCycle: selectedCycle, orderId: orderId, - transactionId: data.transaction_id, + transactionId: data.data.transaction_id, }); toast({ title: '支付成功!', - description: '订阅已激活,正在跳转...', + description: '您的订阅已激活', status: 'success', - duration: 3000, - isClosable: true, - }); - - setTimeout(() => { - onPaymentModalClose(); - window.location.reload(); - }, 2000); - } - } - } catch (error) { - logger.error('SubscriptionContent', 'startAutoPaymentCheck', error); - } - }, 10000); - - setAutoCheckInterval(checkInterval); - }; - - const handleCheckPaymentStatus = async () => { - if (!paymentOrder) return; - - setCheckingPayment(true); - try { - const response = await fetch(`/api/payment/order/${paymentOrder.id}/status`, { - credentials: 'include', - }); - - if (response.ok) { - const data = await response.json(); - if (data.success) { - if (data.payment_success) { - stopAutoPaymentCheck(); - toast({ - title: '支付成功!', - description: '订阅已激活,正在跳转...', - status: 'success', - duration: 3000, - isClosable: true, - }); - - setTimeout(() => { - onPaymentModalClose(); - window.location.reload(); - }, 2000); - } else { - toast({ - title: '支付状态检查', - description: data.message || '还未检测到支付,请继续等待', - status: 'info', duration: 5000, isClosable: true, }); + + onClose(); + setTimeout(() => window.location.reload(), 2000); } - } else { - throw new Error(data.error || '查询失败'); } - } else { - throw new Error('网络错误'); + } catch (error) { + logger.error('SubscriptionContent', 'checkPaymentStatus', error); } - } catch (error) { - toast({ - title: '查询失败', - description: error.message, - status: 'error', - duration: 3000, - isClosable: true, - }); - } finally { - setCheckingPayment(false); - } + }, 3000); + + setAutoCheckInterval(checkInterval as any); }; - const handleForceUpdatePayment = async () => { + const handleForceUpdate = async () => { if (!paymentOrder) return; setForceUpdating(true); try { - const response = await fetch(`/api/payment/order/${paymentOrder.id}/force-update`, { - method: 'POST', + const response = await fetch(`/api/payment/order/${paymentOrder.order_id}/status`, { credentials: 'include', }); if (response.ok) { const data = await response.json(); - if (data.success && data.payment_success) { - stopAutoPaymentCheck(); - + if (data.success && data.data.status === 'paid') { toast({ - title: '状态更新成功!', - description: '订阅已激活,正在刷新页面...', + title: '支付成功!', + description: '您的订阅已激活', status: 'success', - duration: 3000, - isClosable: true, - }); - - setTimeout(() => { - onPaymentModalClose(); - window.location.reload(); - }, 2000); - } else { - toast({ - title: '无法更新状态', - description: data.error || '支付状态未更新', - status: 'warning', duration: 5000, isClosable: true, }); + + onClose(); + setTimeout(() => window.location.reload(), 2000); + } else { + toast({ + title: '未检测到支付', + description: '请确认已完成支付后重试', + status: 'info', + duration: 3000, + isClosable: true, + }); } - } else { - throw new Error('网络错误'); } - } catch (error) { + } catch (error: any) { toast({ - title: '强制更新失败', + title: '查询失败', description: error.message, status: 'error', duration: 3000, @@ -443,22 +377,22 @@ export default function SubscriptionContentNew() { } }; - const getCurrentPrice = (plan) => { + const getCurrentPrice = (plan: any) => { if (!plan || plan.name === 'free') return 0; const option = plan.pricingOptions?.find( - (opt) => opt.cycleKey === selectedCycle + (opt: any) => opt.cycleKey === selectedCycle ); return option ? option.price : plan.pricingOptions[0]?.price || 0; }; - const getCurrentPriceOption = (plan) => { + const getCurrentPriceOption = (plan: any) => { if (!plan || plan.name === 'free') return null; - return plan.pricingOptions?.find((opt) => opt.cycleKey === selectedCycle); + return plan.pricingOptions?.find((opt: any) => opt.cycleKey === selectedCycle); }; - const getIconComponent = (iconName) => { - const icons = { + const getIconComponent = (iconName: string) => { + const icons: any = { star: FaStar, gem: FaGem, crown: FaCrown, @@ -469,225 +403,171 @@ export default function SubscriptionContentNew() { return ( - - {/* 标题区域 */} - - - - - - PRICING - - + {/* 背景光晕 */} + + + + {/* 标题区域 */} + + + 订阅方案 + + + + + - 选择适合你的方案 + 立即开启智能决策 - - - 解锁专业级投资分析工具,让数据为你的决策赋能 - - - - - {/* 当前订阅状态 */} - {user && ( - - - - - - 当前订阅: - - - {user.subscription_type === 'free' - ? '基础版' - : user.subscription_type === 'pro' - ? 'Pro 专业版' - : 'Max 旗舰版'} - - - {user.subscription_status === 'active' ? '已激活' : '未激活'} - - - - {user.subscription_end_date && ( - - 到期时间: {new Date(user.subscription_end_date).toLocaleDateString('zh-CN')} - - )} - - - )} + - {/* 计费周期选择 */} - - - - 选择计费周期 · 时长越长优惠越大 - + {/* 计费周期选择器 */} + + + 选择计费周期 · 时长越长优惠越大 + - - {subscriptionConfig.plans[1]?.pricingOptions?.map((option, index) => ( - - {option.discountPercent > 0 && ( - - 省{option.discountPercent}% - - )} - - - - ))} - + 省{option.discountPercent}% + + )} - {(() => { - const currentOption = subscriptionConfig.plans[1]?.pricingOptions?.find( - (opt) => opt.cycleKey === selectedCycle + + + ))} + + + {(() => { + const currentOption = subscriptionConfig.plans[1]?.pricingOptions?.find( + (opt: any) => opt.cycleKey === selectedCycle + ); + if (currentOption && currentOption.discountPercent > 0) { + return ( + + + + 当前选择可节省 {currentOption.discountPercent}% 的费用 + + ); - if (currentOption && currentOption.discountPercent > 0) { - return ( - - - - 当前选择可节省 {currentOption.discountPercent}% 的费用 - - - ); - } - return null; - })()} - - + } + return null; + })()} + - {/* 套餐卡片 */} - - {subscriptionConfig.plans.map((plan, index) => { + {subscriptionConfig.plans.slice(1).map((plan: any, index: number) => { const IconComponent = getIconComponent(plan.icon); const currentPriceOption = getCurrentPriceOption(plan); const isCurrentPlan = user?.subscription_type === plan.name && - user?.subscription_status === 'active' && - (plan.name === 'free' || user?.billing_cycle === selectedCycle); + user?.subscription_status === 'active'; + + const isPremium = plan.name === 'max'; return ( {/* 推荐标签 */} {plan.badge && ( - - {plan.badge} - + {plan.displayName} )} - {/* 套餐头部 */} - - - - - {plan.displayName} - - - - {plan.description} - - - - {/* 价格展示 */} - {plan.name === 'free' ? ( - - - 免费 + {/* 价格卡片 */} + + + + ¥{getCurrentPrice(plan)} - - ) : ( - - - - ¥ - - - {getCurrentPrice(plan)} - - - /{currentPriceOption?.label || '月'} - - + + /{currentPriceOption?.label || '月'} + + - {currentPriceOption?.originalPrice && ( - - - 原价 ¥{currentPriceOption.originalPrice} - - - 立省 ¥{currentPriceOption.originalPrice - currentPriceOption.price} - - - )} - - )} - - + + {/* 功能列表 */} - - {plan.features.map((feature, idx) => ( - - + {plan.features.map((feature: any, idx: number) => ( + + + > + + {feature.name} {feature.limit && ( - + ({feature.limit}) )} @@ -867,54 +759,12 @@ export default function SubscriptionContentNew() { ))} - - {/* 订阅按钮 */} - ); })} - + {/* FAQ 区域 */} - + 常见问题 - {subscriptionConfig.faqs.map((faq, index) => ( + {subscriptionConfig.faqs.map((faq: any, index: number) => ( setOpenFaqIndex(openFaqIndex === index ? null : index)} justify="space-between" align="center" + cursor="pointer" + onClick={() => setOpenFaqIndex(openFaqIndex === index ? null : index)} > - + {faq.question} @@ -979,8 +826,8 @@ export default function SubscriptionContentNew() { exit={{ height: 0, opacity: 0 }} transition={{ duration: 0.3 }} > - - {faq.answer.split('\n').map((line, idx) => ( + + {faq.answer.split('\n').map((line: string, idx: number) => ( {line} @@ -997,307 +844,57 @@ export default function SubscriptionContentNew() { {/* 支付模态框 */} - { - stopAutoPaymentCheck(); - setPaymentOrder(null); - setPaymentCountdown(0); - setPromoCode(''); - setPromoCodeApplied(false); - setPromoCodeError(''); - setPriceInfo(null); - onPaymentModalClose(); - }} - size="lg" - closeOnOverlayClick={false} - > - - - - - - 微信支付 - - - + + + + 微信支付 + {!paymentOrder ? ( - /* 订单确认 */ - - {selectedPlan && ( - - - 订单确认 - - - - 套餐: - - {selectedPlan.displayName} - - - - 计费周期: - - {getCurrentPriceOption(selectedPlan)?.label || '月付'} - - - - - - {priceInfo && priceInfo.is_upgrade && ( - - - - - {priceInfo.upgrade_type === 'plan_upgrade' - ? '套餐升级' - : priceInfo.upgrade_type === 'cycle_change' - ? '周期变更' - : '套餐和周期调整'} - - - - - 剩余价值抵扣: - -¥{priceInfo.remaining_value.toFixed(2)} - - - - )} - - - 套餐价格: - - ¥{priceInfo ? priceInfo.new_plan_price.toFixed(2) : getCurrentPrice(selectedPlan).toFixed(2)} - - - - {priceInfo && priceInfo.discount_amount > 0 && ( - - 优惠码折扣: - -¥{priceInfo.discount_amount.toFixed(2)} - - )} - - - - - - 实付金额: - - - ¥{priceInfo ? priceInfo.final_amount.toFixed(2) : getCurrentPrice(selectedPlan).toFixed(2)} - - - - - )} - - {/* 优惠码输入 */} - {selectedPlan && ( - - - { - setPromoCode(e.target.value.toUpperCase()); - setPromoCodeError(''); - }} - size="md" - isDisabled={promoCodeApplied} - bg="rgba(255, 255, 255, 0.05)" - border="1px solid" - borderColor={themeColors.border.default} - color={themeColors.text.primary} - _focus={{ - borderColor: themeColors.border.gold, - }} - /> - - - {promoCodeError && ( - - {promoCodeError} - - )} - {promoCodeApplied && priceInfo && ( - - - - 优惠码已应用!节省 ¥{priceInfo.discount_amount.toFixed(2)} - - - - )} - - )} - + + + 套餐: {selectedPlan?.displayName} · {selectedCycle === 'monthly' ? '月付' : selectedCycle === 'quarterly' ? '季付' : selectedCycle === 'semiannual' ? '半年付' : '年付'} + + + ¥{priceInfo?.final_amount || getCurrentPrice(selectedPlan)} + ) : ( - /* 支付二维码 */ - - - 请使用微信扫码支付 + + + 请使用微信扫描二维码完成支付 - - {/* 倒计时 */} + + 剩余时间: {formatTime(paymentCountdown)} + + + - - - - - 支付完成但页面未更新?点击上方"强制更新"按钮 - - - - {/* 支付说明 */} - - • 使用微信"扫一扫"功能扫描上方二维码 - • 支付完成后系统将自动检测并激活订阅 - • 系统每10秒自动检查一次支付状态 - • 如遇问题请联系客服支持 - + 已完成支付 + )}