import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Box,
Button,
Flex,
Text,
Badge,
VStack,
HStack,
useToast,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
useDisclosure,
Image,
Progress,
Divider,
Input,
Icon,
Container,
useBreakpointValue,
} from '@chakra-ui/react';
import {
FaWeixin,
FaGem,
FaCheck,
FaQrcode,
FaClock,
FaRedo,
FaCrown,
FaStar,
FaTimes,
FaChevronDown,
FaChevronUp,
} from 'react-icons/fa';
import { logger } from '../../utils/logger';
import { useAuth } from '../../contexts/AuthContext';
import { useSubscriptionEvents } from '../../hooks/useSubscriptionEvents';
import { subscriptionConfig, themeColors } from '../../views/Pages/Account/subscription-content';
// 计费周期选择器组件 - 移动端垂直布局(年付在上),桌面端水平布局
interface CycleSelectorProps {
options: any[];
selectedCycle: string;
onSelectCycle: (cycle: string) => void;
}
function CycleSelector({ options, selectedCycle, onSelectCycle }: CycleSelectorProps) {
// 使用 useBreakpointValue 动态获取是否是移动端
const isMobile = useBreakpointValue({ base: true, md: false });
// 移动端倒序显示(年付在上),桌面端正常顺序
const displayOptions = isMobile ? [...options].reverse() : options;
return (
{displayOptions.map((option: any) => (
{option.discountPercent > 0 && (
省{option.discountPercent}%
)}
))}
);
}
export default function SubscriptionContentNew() {
const { user } = useAuth();
const subscriptionEvents = useSubscriptionEvents({
currentSubscription: {
plan: user?.subscription_plan || 'free',
status: user?.subscription_status || 'inactive',
},
});
const [selectedCycle, setSelectedCycle] = useState('monthly');
const [selectedPlan, setSelectedPlan] = useState(null);
const [subscriptionPlans, setSubscriptionPlans] = useState([]);
const [priceInfo, setPriceInfo] = useState(null);
const [loading, setLoading] = useState(false);
const [promoCode, setPromoCode] = useState('');
const [promoCodeApplied, setPromoCodeApplied] = useState(false);
const [promoCodeError, setPromoCodeError] = useState('');
const [validatingPromo, setValidatingPromo] = useState(false);
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: any;
if (paymentCountdown > 0) {
timer = setInterval(() => {
setPaymentCountdown((prev) => {
if (prev <= 1) {
handlePaymentExpire();
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => clearInterval(timer);
}, [paymentCountdown]);
// 组件卸载时清理定时器
useEffect(() => {
return () => {
stopAutoPaymentCheck();
};
}, []);
// 组件加载时获取套餐数据
useEffect(() => {
fetchSubscriptionPlans();
}, []);
const fetchSubscriptionPlans = async () => {
try {
logger.debug('SubscriptionContentNew', '正在获取订阅套餐');
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: any) =>
plan &&
plan.name &&
typeof plan.monthly_price === 'number' &&
typeof plan.yearly_price === 'number'
);
logger.debug('SubscriptionContentNew', '套餐加载成功', {
status: response.status,
validPlansCount: validPlans.length,
});
setSubscriptionPlans(validPlans);
} else {
logger.warn('SubscriptionContentNew', '套餐数据格式异常', { data });
setSubscriptionPlans([]);
}
} else {
logger.error('SubscriptionContentNew', 'fetchSubscriptionPlans', new Error(`HTTP ${response.status}`));
setSubscriptionPlans([]);
}
} catch (error) {
logger.error('SubscriptionContentNew', 'fetchSubscriptionPlans', error);
setSubscriptionPlans([]);
}
};
const handlePaymentExpire = () => {
stopAutoPaymentCheck();
toast({
title: '支付二维码已过期',
description: '请重新创建订单',
status: 'warning',
duration: 3000,
isClosable: true,
});
};
const stopAutoPaymentCheck = () => {
if (autoCheckInterval) {
clearInterval(autoCheckInterval);
setAutoCheckInterval(null);
}
};
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: any, cycle: string, promoCodeValue: any = null) => {
try {
const validPromoCode = promoCodeValue && typeof promoCodeValue === 'string' && promoCodeValue.trim()
? promoCodeValue.trim()
: null;
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: any) => {
if (!user) {
toast({
title: '请先登录',
description: '登录后即可订阅',
status: 'warning',
duration: 3000,
isClosable: true,
});
return;
}
subscriptionEvents.trackPricingPlanSelected(
plan.name,
selectedCycle,
getCurrentPrice(plan)
);
setSelectedPlan(plan);
await calculatePrice(plan, selectedCycle, promoCodeApplied ? promoCode : null);
onOpen();
};
const handleCreatePaymentOrder = async () => {
if (!selectedPlan || !user) return;
setLoading(true);
try {
const price = priceInfo?.final_amount || getCurrentPrice(selectedPlan);
// 检查是否为免费升级(剩余价值足够抵扣新套餐价格)
if (price === 0 && priceInfo?.is_upgrade) {
const response = await fetch('/api/subscription/free-upgrade', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
plan_name: selectedPlan.name,
billing_cycle: selectedCycle,
}),
});
const data = await response.json();
if (data.success) {
subscriptionEvents.trackPaymentSuccessful({
planName: selectedPlan.name,
paymentMethod: 'free_upgrade',
amount: 0,
orderId: 'free_upgrade',
transactionId: 'free_upgrade',
});
toast({
title: '升级成功!',
description: data.message,
status: 'success',
duration: 5000,
isClosable: true,
});
onClose();
setTimeout(() => window.location.reload(), 2000);
return;
} else {
throw new Error(data.error || '免费升级失败');
}
}
subscriptionEvents.trackPaymentInitiated({
planName: selectedPlan.name,
paymentMethod: 'wechat_pay',
amount: price,
billingCycle: selectedCycle,
});
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); // 30分钟
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: any) {
subscriptionEvents.trackPaymentFailed(
{
planName: selectedPlan.name,
paymentMethod: 'wechat_pay',
amount: getCurrentPrice(selectedPlan),
},
error.message
);
toast({
title: '创建订单失败',
description: error.message,
status: 'error',
duration: 3000,
isClosable: true,
});
} finally {
setLoading(false);
}
};
const startAutoPaymentCheck = (orderId: string) => {
const checkInterval = setInterval(async () => {
try {
const response = await fetch(`/api/payment/order/${orderId}/status`, {
credentials: 'include',
});
if (response.ok) {
const data = await response.json();
if (data.success && data.data.status === 'paid') {
clearInterval(checkInterval);
setAutoCheckInterval(null);
subscriptionEvents.trackPaymentSuccessful({
planName: selectedPlan?.name,
paymentMethod: 'wechat_pay',
amount: paymentOrder?.amount,
orderId: orderId,
transactionId: data.data.transaction_id,
});
toast({
title: '支付成功!',
description: '您的订阅已激活',
status: 'success',
duration: 5000,
isClosable: true,
});
onClose();
setTimeout(() => window.location.reload(), 2000);
}
}
} catch (error) {
logger.error('SubscriptionContent', 'checkPaymentStatus', error);
}
}, 3000);
setAutoCheckInterval(checkInterval as any);
};
const handleForceUpdate = async () => {
if (!paymentOrder) return;
setForceUpdating(true);
try {
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.data.status === 'paid') {
toast({
title: '支付成功!',
description: '您的订阅已激活',
status: 'success',
duration: 5000,
isClosable: true,
});
onClose();
setTimeout(() => window.location.reload(), 2000);
} else {
toast({
title: '未检测到支付',
description: '请确认已完成支付后重试',
status: 'info',
duration: 3000,
isClosable: true,
});
}
}
} catch (error: any) {
toast({
title: '查询失败',
description: error.message,
status: 'error',
duration: 3000,
isClosable: true,
});
} finally {
setForceUpdating(false);
}
};
// 合并数据库数据和前端配置
const getMergedPlans = () => {
// 如果数据库还没有加载数据,使用静态配置
if (subscriptionPlans.length === 0) {
return subscriptionConfig.plans;
}
// 合并数据库价格和前端UI配置
return subscriptionConfig.plans.map((configPlan: any) => {
const dbPlan = subscriptionPlans.find((p: any) => p.name === configPlan.name);
if (!dbPlan) {
return configPlan; // 如果数据库中没有,使用前端配置
}
// 解析数据库中的 pricing_options JSON
let pricingOptions = configPlan.pricingOptions;
if (dbPlan.pricing_options) {
try {
const parsedOptions = typeof dbPlan.pricing_options === 'string'
? JSON.parse(dbPlan.pricing_options)
: dbPlan.pricing_options;
if (Array.isArray(parsedOptions) && parsedOptions.length > 0) {
pricingOptions = parsedOptions.map((opt: any) => ({
cycleKey: opt.cycle_key,
label: opt.label,
months: opt.months,
price: parseFloat(opt.price),
originalPrice: opt.original_price ? parseFloat(opt.original_price) : null,
discountPercent: opt.discount_percent || 0,
}));
}
} catch (error) {
logger.error('SubscriptionContentNew', '解析pricing_options失败', error);
}
}
// 合并数据,数据库价格优先
return {
...configPlan,
monthly_price: dbPlan.monthly_price,
yearly_price: dbPlan.yearly_price,
pricingOptions: pricingOptions,
displayName: dbPlan.display_name || configPlan.displayName,
description: dbPlan.description || configPlan.description,
};
});
};
const getCurrentPrice = (plan: any) => {
if (!plan || plan.name === 'free') return 0;
const option = plan.pricingOptions?.find(
(opt: any) => opt.cycleKey === selectedCycle
);
return option ? option.price : plan.pricingOptions?.[0]?.price || 0;
};
const getCurrentPriceOption = (plan: any) => {
if (!plan || plan.name === 'free') return null;
return plan.pricingOptions?.find((opt: any) => opt.cycleKey === selectedCycle);
};
const getIconComponent = (iconName: string) => {
const icons: any = {
star: FaStar,
gem: FaGem,
crown: FaCrown,
};
return icons[iconName] || FaStar;
};
// 获取按钮文字
const getButtonText = (plan: any) => {
const currentPlanName = user?.subscription_type;
const isActive = user?.subscription_status === 'active';
if (!isActive || !currentPlanName || currentPlanName === 'free') {
return `选择${plan.displayName}`;
}
if (currentPlanName === plan.name) {
// 同级续费
return `续费${plan.displayName}`;
}
// 升级或降级
if (currentPlanName === 'pro' && plan.name === 'max') {
return `升级为${plan.displayName}`;
}
if (currentPlanName === 'max' && plan.name === 'pro') {
return `到期后切换到${plan.displayName}`;
}
return `选择${plan.displayName}`;
};
// 判断按钮是否可点击
const isButtonDisabled = (plan: any) => {
return false; // 所有套餐都可以选择,包括当前套餐(续费)
};
return (
{/* 背景光晕 */}
{/* 标题区域 */}
订阅方案
立即开启智能决策
{/* 当前订阅状态 */}
{user && user.subscription_type && user.subscription_type !== 'free' && user.subscription_status === 'active' && (
当前订阅: {user.subscription_type === 'max' ? 'Max 旗舰版' : 'Pro 专业版'}
{user.billing_cycle && (
{user.billing_cycle === 'monthly' ? '月付' :
user.billing_cycle === 'quarterly' ? '季付' :
user.billing_cycle === 'semiannual' ? '半年付' :
user.billing_cycle === 'yearly' ? '年付' : user.billing_cycle}
)}
使用中
到期时间
{user.subscription_end_date
? new Date(user.subscription_end_date).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
: '永久有效'
}
{user.subscription_end_date && (() => {
const endDate = new Date(user.subscription_end_date);
const today = new Date();
const daysLeft = Math.ceil((endDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
if (daysLeft > 0 && daysLeft <= 30) {
return (
⚠️ 还有 {daysLeft} 天到期,记得及时续费哦
);
}
return null;
})()}
)}
{/* 计费周期选择器 */}
选择计费周期 · 时长越长优惠越大
{(() => {
const currentOption = getMergedPlans()[1]?.pricingOptions?.find(
(opt: any) => opt.cycleKey === selectedCycle
);
if (currentOption && currentOption.discountPercent > 0) {
return (
当前选择可节省 {currentOption.discountPercent}% 的费用
);
}
return null;
})()}
{/* 套餐卡片 - 借鉴 index.pug 设计 */}
{getMergedPlans().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';
const isPremium = plan.name === 'max';
return (
{/* 套餐标题 */}
{plan.displayName}
{/* 价格卡片 */}
¥{getCurrentPrice(plan)}
/ {currentPriceOption?.label || '月'}
{/* 功能列表 */}
{plan.features.map((feature: any, idx: number) => (
{feature.name}
{feature.limit && (
({feature.limit})
)}
))}
);
})}
{/* FAQ 区域 */}
常见问题
{subscriptionConfig.faqs.map((faq: any, index: number) => (
setOpenFaqIndex(openFaqIndex === index ? null : index)}
>
{faq.question}
{openFaqIndex === index && (
{faq.answer.split('\n').map((line: string, idx: number) => (
{line}
))}
)}
))}
{/* 支付模态框 */}
微信支付
{!paymentOrder ? (
{/* 订阅类型提示 */}
{selectedPlan && priceInfo && (
<>
{priceInfo.is_upgrade && (
{priceInfo.final_amount === 0
? `恭喜!您的当前订阅剩余价值足够直接升级到${selectedPlan.displayName},无需支付额外费用!`
: `升级到${selectedPlan.displayName},立即生效!按差价补缴费用`}
)}
{priceInfo.is_downgrade && (
当前{priceInfo.current_plan?.toUpperCase()}订阅到期后自动切换到{selectedPlan.displayName}
)}
{priceInfo.is_renewal && (
续费{selectedPlan.displayName},在当前到期日基础上延长时长
)}
>
)}
{/* 价格明细 */}
{selectedPlan && priceInfo && (
{selectedPlan.displayName} · {selectedCycle === 'monthly' ? '月付' : selectedCycle === 'quarterly' ? '季付' : selectedCycle === 'semiannual' ? '半年付' : '年付'}
¥{priceInfo.original_price?.toFixed(2) || getCurrentPrice(selectedPlan).toFixed(2)}
{/* 升级抵扣价值 */}
{priceInfo.is_upgrade && priceInfo.remaining_value > 0 && (
当前订阅剩余价值抵扣
-¥{priceInfo.remaining_value.toFixed(2)}
)}
{/* 优惠码折扣 */}
{promoCodeApplied && priceInfo.discount_amount > 0 && (
优惠码折扣
-¥{priceInfo.discount_amount.toFixed(2)}
)}
实付金额:
¥{priceInfo.final_amount.toFixed(2)}
)}
{/* 优惠码输入 */}
{selectedPlan && (
{
setPromoCode(e.target.value.toUpperCase());
setPromoCodeError('');
}}
size="md"
isDisabled={promoCodeApplied}
bg="rgba(255, 255, 255, 0.05)"
border="1px solid rgba(255, 255, 255, 0.1)"
color="white"
_placeholder={{ color: 'rgba(255, 255, 255, 0.4)' }}
_hover={{ borderColor: 'rgba(212, 175, 55, 0.3)' }}
_focus={{ borderColor: '#D4AF37', boxShadow: '0 0 0 1px #D4AF37' }}
/>
{promoCodeError && (
{promoCodeError}
)}
{promoCodeApplied && priceInfo && (
优惠码已应用!节省 ¥{priceInfo.discount_amount.toFixed(2)}
)}
)}
) : (
请使用微信扫描二维码完成支付
{/* 倒计时 */}
二维码有效时间: {formatTime(paymentCountdown)}
{/* 二维码 */}
{paymentOrder.qr_code_url ? (
) : (
)}
{/* 订单信息 */}
订单号: {paymentOrder.order_no}
支付金额:
¥{paymentOrder.amount}
{/* 操作按钮 */}
}
onClick={handleForceUpdate}
isLoading={forceUpdating}
_hover={{
bgGradient: 'linear-gradient(135deg, #F4E3A7, #D4AF37)',
}}
>
已完成支付
)}
);
}