// src/components/Subscription/SubscriptionContent.js import { Box, Button, Flex, Grid, Icon, Text, Badge, VStack, HStack, useColorModeValue, useToast, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, useDisclosure, Image, Progress, Divider, Table, Thead, Tbody, Tr, Th, Td, Heading, Collapse, Input, InputGroup, InputRightElement, } from '@chakra-ui/react'; import React, { useState, useEffect } from 'react'; import { logger } from '../../utils/logger'; import { useAuth } from '../../contexts/AuthContext'; import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents'; // Icons import { FaWeixin, FaGem, FaCheck, FaQrcode, FaClock, FaRedo, FaCrown, FaStar, FaTimes, FaInfinity, FaChevronDown, FaChevronUp, } from 'react-icons/fa'; export default function SubscriptionContent() { // Auth context const { user } = useAuth(); // 🎯 初始化订阅埋点Hook(传入当前订阅信息) const subscriptionEvents = useSubscriptionEvents({ currentSubscription: { plan: user?.subscription_plan || 'free', status: user?.subscription_status || 'none' } }); // Chakra color mode const textColor = useColorModeValue('gray.700', 'white'); const borderColor = useColorModeValue('gray.200', 'gray.600'); const bgCard = useColorModeValue('white', 'gray.800'); const bgAccent = useColorModeValue('blue.50', 'blue.900'); const secondaryText = useColorModeValue('gray.600', 'gray.400'); const toast = useToast(); const { isOpen: isPaymentModalOpen, onOpen: onPaymentModalOpen, onClose: onPaymentModalClose } = useDisclosure(); // State const [subscriptionPlans, setSubscriptionPlans] = useState([]); const [selectedPlan, setSelectedPlan] = useState(null); const [selectedCycle, setSelectedCycle] = useState('monthly'); const [paymentOrder, setPaymentOrder] = 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); // 优惠码相关state const [promoCode, setPromoCode] = useState(''); const [promoCodeApplied, setPromoCodeApplied] = useState(false); const [promoCodeError, setPromoCodeError] = useState(''); const [validatingPromo, setValidatingPromo] = useState(false); const [priceInfo, setPriceInfo] = useState(null); // 价格信息(包含升级计算) // 加载订阅套餐数据 useEffect(() => { fetchSubscriptionPlans(); // 不再需要 fetchCurrentUser(),直接使用 AuthContext 的 user }, []); // 倒计时更新 useEffect(() => { let timer; if (paymentCountdown > 0) { timer = setInterval(() => { setPaymentCountdown(prev => { if (prev <= 1) { handlePaymentExpired(); return 0; } return prev - 1; }); }, 1000); } return () => clearInterval(timer); }, [paymentCountdown]); // 组件卸载时清理定时器 useEffect(() => { return () => { stopAutoPaymentCheck(); }; }, []); const fetchSubscriptionPlans = async () => { try { logger.debug('SubscriptionContent', '正在获取订阅套餐'); const response = await fetch('/api/subscription/plans'); if (response.ok) { const data = await response.json(); if (data.success && Array.isArray(data.data)) { const validPlans = data.data.filter(plan => plan && plan.name && typeof plan.monthly_price === 'number' && typeof plan.yearly_price === 'number' ); logger.debug('SubscriptionContent', '套餐加载成功', { status: response.status, validPlansCount: validPlans.length }); setSubscriptionPlans(validPlans); } else { logger.warn('SubscriptionContent', '套餐数据格式异常', { data }); setSubscriptionPlans([]); } } else { logger.error('SubscriptionContent', 'fetchSubscriptionPlans', new Error(`HTTP ${response.status}`)); setSubscriptionPlans([]); } } catch (error) { logger.error('SubscriptionContent', 'fetchSubscriptionPlans', error); setSubscriptionPlans([]); } }; // 计算价格(包含升级和优惠码) const calculatePrice = async (plan, cycle, promoCodeValue = null) => { try { // 确保优惠码值正确:只接受非空字符串,其他情况传null const validPromoCode = promoCodeValue && typeof promoCodeValue === 'string' && promoCodeValue.trim() ? promoCodeValue.trim() : null; logger.debug('SubscriptionContent', '计算价格', { plan: plan.name, cycle, promoCodeValue, validPromoCode }); const response = await fetch('/api/subscription/calculate-price', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ to_plan: plan.name, to_cycle: cycle, promo_code: validPromoCode }) }); if (response.ok) { const data = await response.json(); if (data.success) { setPriceInfo(data.data); return data.data; } } return null; } catch (error) { logger.error('SubscriptionContent', 'calculatePrice', error); return null; } }; // 验证优惠码 const handleValidatePromoCode = async () => { const trimmedCode = promoCode.trim(); if (!trimmedCode) { setPromoCodeError('请输入优惠码'); return; } if (!selectedPlan) { setPromoCodeError('请先选择套餐'); return; } setValidatingPromo(true); setPromoCodeError(''); try { // 重新计算价格,包含优惠码(使用去除空格后的值) const result = await calculatePrice(selectedPlan, selectedCycle, trimmedCode); if (result && !result.promo_error) { setPromoCodeApplied(true); toast({ title: '优惠码已应用', description: `节省 ¥${result.discount_amount.toFixed(2)}`, status: 'success', duration: 3000, isClosable: true, }); } else { setPromoCodeError(result?.promo_error || '优惠码无效'); setPromoCodeApplied(false); } } catch (error) { setPromoCodeError('验证失败,请重试'); setPromoCodeApplied(false); } finally { setValidatingPromo(false); } }; // 移除优惠码 const handleRemovePromoCode = async () => { setPromoCode(''); setPromoCodeApplied(false); setPromoCodeError(''); // 重新计算价格(不含优惠码) if (selectedPlan) { await calculatePrice(selectedPlan, selectedCycle, null); } }; const handleSubscribe = async (plan) => { if (!user) { toast({ title: '请先登录', status: 'warning', duration: 3000, isClosable: true, }); return; } if (!plan || !plan.name) { toast({ title: '套餐信息错误', status: 'error', duration: 3000, isClosable: true, }); return; } // 🎯 追踪定价方案选择 subscriptionEvents.trackPricingPlanSelected( plan.name, selectedCycle, selectedCycle === 'monthly' ? plan.monthly_price : plan.yearly_price ); setSelectedPlan(plan); // 计算价格(包含升级判断) await calculatePrice(plan, selectedCycle, promoCodeApplied ? promoCode : null); onPaymentModalOpen(); }; const handleCreateOrder = async () => { if (!selectedPlan) return; setLoading(true); try { const price = priceInfo?.final_amount || (selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price); // 🎯 追踪支付发起 subscriptionEvents.trackPaymentInitiated({ planName: selectedPlan.name, paymentMethod: 'wechat_pay', amount: price, billingCycle: selectedCycle, orderId: null // Will be set after order creation }); const response = await fetch('/api/payment/create-order', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ plan_name: selectedPlan.name, billing_cycle: selectedCycle, promo_code: promoCodeApplied ? promoCode : null }) }); if (response.ok) { const data = await response.json(); if (data.success) { setPaymentOrder(data.data); setPaymentCountdown(30 * 60); startAutoPaymentCheck(data.data.id); toast({ title: '订单创建成功', description: '请使用微信扫描二维码完成支付', status: 'success', duration: 3000, isClosable: true, }); } else { throw new Error(data.message || '创建订单失败'); } } else { throw new Error('网络错误'); } } catch (error) { // 🎯 追踪支付失败 subscriptionEvents.trackPaymentFailed({ planName: selectedPlan.name, paymentMethod: 'wechat_pay', amount: selectedCycle === 'monthly' ? selectedPlan.monthly_price : selectedPlan.yearly_price }, error.message); toast({ title: '创建订单失败', description: error.message, status: 'error', duration: 3000, isClosable: true, }); } finally { setLoading(false); } }; const handlePaymentExpired = () => { setPaymentOrder(null); setPaymentCountdown(0); stopAutoPaymentCheck(); toast({ title: '支付二维码已过期', description: '请重新创建订单', status: 'warning', duration: 3000, isClosable: true, }); }; const startAutoPaymentCheck = (orderId) => { logger.info('SubscriptionContent', '开始自动检查支付状态', { orderId }); const checkInterval = setInterval(async () => { try { const response = await fetch(`/api/payment/order/${orderId}/status`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); logger.debug('SubscriptionContent', '支付状态检查结果', { orderId, paymentSuccess: data.payment_success, data }); if (data.success && data.payment_success) { clearInterval(checkInterval); setAutoCheckInterval(null); logger.info('SubscriptionContent', '自动检测到支付成功', { orderId }); // 🎯 追踪支付成功 subscriptionEvents.trackPaymentSuccessful({ planName: selectedPlan?.name, paymentMethod: 'wechat_pay', amount: paymentOrder?.amount, billingCycle: selectedCycle, orderId: orderId, transactionId: data.transaction_id }); // 🎯 追踪订阅创建 subscriptionEvents.trackSubscriptionCreated({ plan: selectedPlan?.name, billingCycle: selectedCycle, amount: paymentOrder?.amount, startDate: new Date().toISOString(), endDate: null // Will be calculated by backend }); toast({ title: '支付成功!', description: '订阅已激活,正在跳转...', status: 'success', duration: 3000, isClosable: true, }); setTimeout(() => { onPaymentModalClose(); window.location.reload(); }, 2000); } } } catch (error) { logger.error('SubscriptionContent', 'startAutoPaymentCheck', error, { orderId }); } }, 10000); setAutoCheckInterval(checkInterval); }; const stopAutoPaymentCheck = () => { if (autoCheckInterval) { clearInterval(autoCheckInterval); setAutoCheckInterval(null); logger.debug('SubscriptionContent', '停止自动检查支付状态'); } }; const handleRefreshUserStatus = () => { // 刷新页面以重新加载用户数据 window.location.reload(); }; const handleForceUpdatePayment = async () => { if (!paymentOrder) return; setForceUpdating(true); try { const response = await fetch(`/api/payment/order/${paymentOrder.id}/force-update`, { method: 'POST', credentials: 'include' }); if (response.ok) { const data = await response.json(); logger.info('SubscriptionContent', '强制更新支付状态结果', { orderId: paymentOrder.id, paymentSuccess: data.payment_success, data }); if (data.success && data.payment_success) { stopAutoPaymentCheck(); toast({ 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, }); } } else { throw new Error('网络错误'); } } catch (error) { logger.error('SubscriptionContent', 'handleForceUpdatePayment', error, { orderId: paymentOrder?.id }); toast({ title: '强制更新失败', description: error.message, status: 'error', duration: 3000, isClosable: true, }); } finally { setForceUpdating(false); } }; 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(); logger.info('SubscriptionContent', '手动检查支付状态结果', { orderId: paymentOrder.id, paymentSuccess: data.payment_success, data: data.data }); if (data.success) { if (data.payment_success) { stopAutoPaymentCheck(); logger.info('SubscriptionContent', '手动检测到支付成功', { orderId: paymentOrder.id }); 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, }); } } else { throw new Error(data.error || '查询失败'); } } else { throw new Error('网络错误'); } } catch (error) { logger.error('SubscriptionContent', 'handleCheckPaymentStatus', error, { orderId: paymentOrder?.id }); toast({ title: '查询失败', description: error.message, status: 'error', duration: 3000, isClosable: true, }); } finally { setCheckingPayment(false); } }; const formatTime = (seconds) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; }; const getCurrentPrice = (plan) => { if (!plan) return 0; return selectedCycle === 'monthly' ? plan.monthly_price : plan.yearly_price; }; const getSavingsText = (plan) => { if (!plan || selectedCycle !== 'yearly') return null; const yearlyTotal = plan.monthly_price * 12; const savings = yearlyTotal - plan.yearly_price; const percentage = Math.round((savings / yearlyTotal) * 100); return `年付节省 ${percentage}%`; }; // 获取按钮文字(根据用户当前订阅判断是升级还是新订阅) const getButtonText = (plan, user) => { if (!user || user.subscription_type === 'free') { return `选择 ${plan.display_name}`; } // 判断是否为升级 const planLevels = { 'free': 0, 'pro': 1, 'max': 2 }; const currentLevel = planLevels[user.subscription_type] || 0; const targetLevel = planLevels[plan.name] || 0; if (targetLevel > currentLevel) { return `升级至 ${plan.display_name}`; } else if (targetLevel < currentLevel) { return `切换至 ${plan.display_name}`; } else { // 同级别,可能是切换周期 return `切换至 ${plan.display_name}`; } }; // 统一的功能列表定义 - 基于商业定价(10月15日)文档 const allFeatures = [ // 新闻催化分析模块 { name: '新闻信息流', free: true, pro: true, max: true }, { name: '历史事件对比', free: 'TOP3', pro: true, max: true }, { name: '事件传导链分析(AI)', free: '有限体验', pro: true, max: true }, { name: '事件-相关标的分析', free: false, pro: true, max: true }, { name: '相关概念展示', free: false, pro: true, max: true }, { name: '板块深度分析(AI)', free: false, pro: false, max: true }, // 个股中心模块 { name: 'AI复盘功能', free: true, pro: true, max: true }, { name: '企业概览', free: '限制预览', pro: true, max: true }, { name: '个股深度分析(AI)', free: '10家/月', pro: '50家/月', max: true }, { name: '高效数据筛选工具', free: false, pro: true, max: true }, // 概念中心模块 { name: '概念中心(548大概念)', free: 'TOP5', pro: true, max: true }, { name: '历史时间轴查询', free: false, pro: '100天', max: true }, { name: '概念高频更新', free: false, pro: false, max: true }, // 涨停分析模块 { name: '涨停板块数据分析', free: true, pro: true, max: true }, { name: '个股涨停分析', free: true, pro: true, max: true }, ]; return ( {/* 当前订阅状态 */} {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')} )} {user.subscription_status === 'active' && user.subscription_type !== 'free' && ( )} )} {/* 计费周期选择 */} {/* 订阅套餐 */} {subscriptionPlans.length === 0 ? ( 正在加载订阅套餐... ) : ( <> {/* 免费版套餐 */} {/* 套餐头部 - 图标与标题同行 */} 基础版 免费 免费体验核心功能,7项实用工具 {/* 功能列表 */} {allFeatures.map((feature, index) => { const hasFreeAccess = feature.free === true || typeof feature.free === 'string'; const freeLimit = typeof feature.free === 'string' ? feature.free : null; return ( {feature.name} {freeLimit && ( ({freeLimit}) )} ); })} {/* 订阅按钮 */} {/* 付费套餐 */} {subscriptionPlans.filter(plan => plan && plan.name).map((plan) => ( {/* 推荐标签 */} {plan.name === 'max' && ( 🔥 最受欢迎 )} {/* 套餐头部 - 图标与标题同行 */} {plan.display_name} ¥ {getCurrentPrice(plan).toFixed(0)} /{selectedCycle === 'monthly' ? '月' : '年'} {plan.description} {getSavingsText(plan) && ( {getSavingsText(plan)} )} {/* 功能列表 */} {allFeatures.map((feature, index) => { const featureValue = feature[plan.name]; const isSupported = featureValue === true || typeof featureValue === 'string'; const limitText = typeof featureValue === 'string' ? featureValue : null; return ( {feature.name} {limitText && ( ({limitText}) )} ); })} {/* 订阅按钮 */} ))} )} {/* FAQ 常见问题 */} 常见问题 {/* FAQ 1 */} setOpenFaqIndex(openFaqIndex === 0 ? null : 0)} bg={openFaqIndex === 0 ? bgAccent : 'transparent'} _hover={{ bg: bgAccent }} transition="all 0.2s" justify="space-between" align="center" > 如何取消订阅? 您可以随时在账户设置中取消订阅。取消后,您的订阅将在当前计费周期结束时到期,期间您仍可继续使用付费功能。取消后不会立即扣款,也不会自动续费。 {/* FAQ 2 */} setOpenFaqIndex(openFaqIndex === 1 ? null : 1)} bg={openFaqIndex === 1 ? bgAccent : 'transparent'} _hover={{ bg: bgAccent }} transition="all 0.2s" justify="space-between" align="center" > 支持哪些支付方式? 我们目前支持微信支付。扫描支付二维码后,系统会自动检测支付状态并激活您的订阅。支付过程安全可靠,所有交易都经过加密处理。 {/* FAQ 3 */} setOpenFaqIndex(openFaqIndex === 2 ? null : 2)} bg={openFaqIndex === 2 ? bgAccent : 'transparent'} _hover={{ bg: bgAccent }} transition="all 0.2s" justify="space-between" align="center" > 可以在月付和年付之间切换吗? 可以。您可以随时更改计费周期。如果从月付切换到年付,系统会计算剩余价值并应用到新的订阅中。年付用户可享受20%的折扣优惠。 {/* FAQ 4 */} setOpenFaqIndex(openFaqIndex === 3 ? null : 3)} bg={openFaqIndex === 3 ? bgAccent : 'transparent'} _hover={{ bg: bgAccent }} transition="all 0.2s" justify="space-between" align="center" > 是否提供退款? 我们提供7天无理由退款保证。如果您在订阅后7天内对服务不满意,可以申请全额退款。超过7天后,我们将根据实际使用情况进行评估。 {/* FAQ 5 */} setOpenFaqIndex(openFaqIndex === 4 ? null : 4)} bg={openFaqIndex === 4 ? bgAccent : 'transparent'} _hover={{ bg: bgAccent }} transition="all 0.2s" justify="space-between" align="center" > Pro版和Max版有什么区别? Pro版适合个人专业用户,提供高级图表、历史数据分析等功能。Max版则是为团队和企业设计,额外提供实时数据推送、API访问、无限制的数据存储和团队协作功能,并享有优先技术支持。 {/* 支付模态框 */} {isPaymentModalOpen && ( { stopAutoPaymentCheck(); setPaymentOrder(null); setPaymentCountdown(0); // 清空优惠码状态 setPromoCode(''); setPromoCodeApplied(false); setPromoCodeError(''); setPriceInfo(null); onPaymentModalClose(); }} size="lg" closeOnOverlayClick={false} > 微信支付 {!paymentOrder ? ( /* 订单确认 */ {selectedPlan ? ( 订单确认 套餐: {selectedPlan.display_name} 计费周期: {selectedCycle === 'monthly' ? '按月付费' : '按年付费'} {/* 价格明细 */} {priceInfo && priceInfo.is_upgrade && ( {priceInfo.upgrade_type === 'plan_upgrade' ? '套餐升级' : priceInfo.upgrade_type === 'cycle_change' ? '周期变更' : '套餐和周期调整'} 当前订阅: {priceInfo.current_plan === 'pro' ? 'Pro版' : 'Max版'} ({priceInfo.current_cycle === 'monthly' ? '月付' : '年付'}) 剩余价值: ¥{priceInfo.remaining_value.toFixed(2)} )} {priceInfo && priceInfo.is_upgrade ? '新套餐价格:' : '套餐价格:'} ¥{priceInfo ? priceInfo.new_plan_price.toFixed(2) : getCurrentPrice(selectedPlan).toFixed(2)} {priceInfo && priceInfo.is_upgrade && priceInfo.remaining_value > 0 && ( 已付剩余抵扣: -¥{priceInfo.remaining_value.toFixed(2)} )} {priceInfo && priceInfo.discount_amount > 0 && ( 优惠码折扣: -¥{priceInfo.discount_amount.toFixed(2)} )} 实付金额: ¥{priceInfo ? priceInfo.final_amount.toFixed(2) : getCurrentPrice(selectedPlan).toFixed(2)} {getSavingsText(selectedPlan) && !priceInfo?.is_upgrade && ( {getSavingsText(selectedPlan)} )} ) : ( 请先选择一个订阅套餐 )} {/* 优惠码输入 */} {selectedPlan && ( { setPromoCode(e.target.value.toUpperCase()); setPromoCodeError(''); }} size="md" isDisabled={promoCodeApplied} /> {promoCodeError && ( {promoCodeError} )} {promoCodeApplied && priceInfo && ( 优惠码已应用!节省 ¥{priceInfo.discount_amount.toFixed(2)} )} )} ) : ( /* 支付二维码 */ 请使用微信扫码支付 {/* 倒计时 */} 二维码有效时间: {formatTime(paymentCountdown)} {/* 二维码 */} {paymentOrder.qr_code_url ? ( 微信支付二维码 ) : ( )} {/* 订单信息 */} 订单号: {paymentOrder.order_no} 支付金额: ¥{paymentOrder.amount} {/* 操作按钮 */} 支付完成但页面未更新?点击上方"强制更新"按钮 {/* 支付状态提示 */} {autoCheckInterval && ( 🔄 正在自动检查支付状态... )} {/* 支付说明 */} • 使用微信"扫一扫"功能扫描上方二维码 • 支付完成后系统将自动检测并激活订阅 • 系统每10秒自动检查一次支付状态 • 如遇问题请联系客服支持 )} )} ); }