update pay function

This commit is contained in:
2025-11-20 07:46:50 +08:00
parent f515dc94f4
commit 4a1157c0b6
3 changed files with 285 additions and 48 deletions

View File

@@ -51,7 +51,7 @@ export default function SubscriptionContentNew() {
},
});
const [selectedCycle, setSelectedCycle] = useState('yearly');
const [selectedCycle, setSelectedCycle] = useState('monthly');
const [selectedPlan, setSelectedPlan] = useState(null);
const [subscriptionPlans, setSubscriptionPlans] = useState([]);
const [priceInfo, setPriceInfo] = useState(null);
@@ -491,6 +491,37 @@ export default function SubscriptionContentNew() {
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 (
<Box
minH="100vh"
@@ -562,6 +593,115 @@ export default function SubscriptionContentNew() {
</motion.div>
</VStack>
{/* 当前订阅状态 */}
{user && user.subscription_type && user.subscription_type !== 'free' && user.subscription_status === 'active' && (
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<Box
mb={12}
p={6}
borderRadius="2xl"
bg="rgba(212, 175, 55, 0.05)"
border="2px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(20px)"
maxW="600px"
mx="auto"
position="relative"
overflow="hidden"
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '4px',
bgGradient: 'linear-gradient(90deg, rgba(212, 175, 55, 0.5), rgba(212, 175, 55, 1), rgba(212, 175, 55, 0.5))',
}}
>
<VStack spacing={4} align="stretch">
<HStack justify="space-between" align="center">
<HStack spacing={3}>
<Icon
as={user.subscription_type === 'max' ? FaCrown : FaGem}
color="#D4AF37"
boxSize={6}
/>
<VStack align="start" spacing={0}>
<Text color="white" fontSize="lg" fontWeight="bold">
: {user.subscription_type === 'max' ? 'Max 旗舰版' : 'Pro 专业版'}
</Text>
<Text color="rgba(255, 255, 255, 0.6)" fontSize="xs">
{user.billing_cycle === 'monthly' ? '月付' :
user.billing_cycle === 'quarterly' ? '季付' :
user.billing_cycle === 'semiannual' ? '半年付' : '年付'}
</Text>
</VStack>
</HStack>
<Badge
px={3}
py={1}
borderRadius="full"
bg="rgba(76, 175, 80, 0.2)"
border="1px solid rgba(76, 175, 80, 0.4)"
color="green.300"
fontSize="xs"
fontWeight="medium"
>
使
</Badge>
</HStack>
<Divider borderColor="rgba(212, 175, 55, 0.2)" />
<Flex justify="space-between" align="center">
<HStack spacing={2}>
<Icon as={FaClock} color="rgba(212, 175, 55, 0.8)" boxSize={4} />
<Text color="rgba(255, 255, 255, 0.7)" fontSize="sm">
</Text>
</HStack>
<Text color="#D4AF37" fontSize="md" fontWeight="bold">
{user.end_date
? new Date(user.end_date).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
: '永久有效'
}
</Text>
</Flex>
{user.end_date && (() => {
const endDate = new Date(user.end_date);
const today = new Date();
const daysLeft = Math.ceil((endDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
if (daysLeft > 0 && daysLeft <= 30) {
return (
<Box
p={2}
borderRadius="md"
bg="rgba(255, 165, 0, 0.1)"
border="1px solid rgba(255, 165, 0, 0.3)"
>
<Text color="orange.300" fontSize="xs" textAlign="center">
{daysLeft}
</Text>
</Box>
);
}
return null;
})()}
</VStack>
</Box>
</motion.div>
)}
{/* 计费周期选择器 */}
<VStack spacing={6} mb={16}>
<Text fontSize="md" color="rgba(255, 255, 255, 0.7)">
@@ -791,55 +931,41 @@ export default function SubscriptionContentNew() {
size="lg"
h="56px"
bg={
isCurrentPlan
? 'transparent'
: isPremium
isPremium
? 'linear-gradient(135deg, #D4AF37 0%, #B8941F 100%)'
: 'rgba(255, 255, 255, 0.05)'
}
color={
isCurrentPlan
? 'rgba(255, 255, 255, 0.5)'
: isPremium
isPremium
? '#000'
: '#fff'
}
border={
isCurrentPlan
? '1px solid rgba(255, 255, 255, 0.2)'
: isPremium
isPremium
? 'none'
: '1px solid rgba(255, 255, 255, 0.1)'
}
fontWeight="bold"
fontSize="md"
borderRadius="lg"
onClick={() => !isCurrentPlan && handleSubscribe(plan)}
isDisabled={isCurrentPlan}
cursor={isCurrentPlan ? 'not-allowed' : 'pointer'}
_hover={
!isCurrentPlan
? {
transform: 'translateY(-2px)',
shadow: isPremium
? '0 8px 30px rgba(212, 175, 55, 0.4)'
: '0 8px 20px rgba(255, 255, 255, 0.1)',
bg: isPremium
? 'linear-gradient(135deg, #E5C047 0%, #C9A52F 100%)'
: 'rgba(255, 255, 255, 0.08)',
}
: {}
}
_active={
!isCurrentPlan
? {
transform: 'translateY(0)',
}
: {}
}
onClick={() => handleSubscribe(plan)}
isDisabled={isButtonDisabled(plan)}
cursor="pointer"
_hover={{
transform: 'translateY(-2px)',
shadow: isPremium
? '0 8px 30px rgba(212, 175, 55, 0.4)'
: '0 8px 20px rgba(255, 255, 255, 0.1)',
bg: isPremium
? 'linear-gradient(135deg, #E5C047 0%, #C9A52F 100%)'
: 'rgba(255, 255, 255, 0.08)',
}}
_active={{
transform: 'translateY(0)',
}}
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
>
{isCurrentPlan ? '当前套餐' : `选择${plan.displayName}`}
{getButtonText(plan)}
</Button>
</Box>
@@ -980,6 +1106,57 @@ export default function SubscriptionContentNew() {
<ModalBody pb={6}>
{!paymentOrder ? (
<VStack spacing={6} align="stretch">
{/* 订阅类型提示 */}
{selectedPlan && priceInfo && (
<>
{priceInfo.is_upgrade && (
<Box
p={3}
bg="rgba(76, 175, 80, 0.1)"
borderRadius="lg"
border="1px solid rgba(76, 175, 80, 0.3)"
>
<HStack spacing={2}>
<Icon as={FaCheck} color="green.400" />
<Text color="green.400" fontSize="sm" fontWeight="medium">
{selectedPlan.displayName}
</Text>
</HStack>
</Box>
)}
{priceInfo.is_downgrade && (
<Box
p={3}
bg="rgba(255, 165, 0, 0.1)"
borderRadius="lg"
border="1px solid rgba(255, 165, 0, 0.3)"
>
<HStack spacing={2}>
<Icon as={FaClock} color="orange.400" />
<Text color="orange.400" fontSize="sm" fontWeight="medium">
{priceInfo.current_plan?.toUpperCase()}{selectedPlan.displayName}
</Text>
</HStack>
</Box>
)}
{priceInfo.is_renewal && (
<Box
p={3}
bg="rgba(33, 150, 243, 0.1)"
borderRadius="lg"
border="1px solid rgba(33, 150, 243, 0.3)"
>
<HStack spacing={2}>
<Icon as={FaRedo} color="blue.400" />
<Text color="blue.400" fontSize="sm" fontWeight="medium">
{selectedPlan.displayName}
</Text>
</HStack>
</Box>
)}
</>
)}
{/* 价格明细 */}
{selectedPlan && priceInfo && (
<Box

View File

@@ -158,12 +158,16 @@ export const subscriptionConfig = {
answer: '我们目前支持微信支付。扫描支付二维码后,系统会自动检测支付状态并激活您的订阅。支付过程安全可靠,所有交易都经过加密处理。',
},
{
question: '升级或切换套餐时,原套餐的费用怎么办',
answer: '当您升级套餐或切换计费周期时,系统会自动计算您当前订阅的剩余价值并用于抵扣新套餐的费用。\n\n计算方式\n• 剩余价值 = 原套餐价格 × (剩余天数 / 总天数)\n• 实付金额 = 新套餐价格 - 剩余价值 - 优惠码折扣\n\n例如您购买了年付Pro版¥2699,使用了180天后升级到Max版¥5399/年),剩余价值约¥1350将自动抵扣,实付约¥4049。',
question: '同级续费如何计费',
answer: '如果您是Pro用户续费Pro版本或Max用户续费Max版本支付后将在当前订阅到期日基础上延长相应时长。例如您的Max年付版本还有30天到期续费Max年付后新的到期时间将延长至395天后30天+365天。',
},
{
question: '可以在不同计费周期之间切换吗',
answer: '可以。您可以随时更改计费周期。如果从短期切换到长期,系统会计算剩余价值并应用到新的订阅中。长期套餐(季付、半年付、年付)可享受更大的折扣优惠。',
question: 'Pro用户如何升级到Max',
answer: '从Pro升级到Max需要补差价升级后立即生效。系统会根据您Pro订阅的剩余价值计算需要补缴的费用。支付成功后您将立即获得Max版本的所有功能。',
},
{
question: 'Max用户可以切换到Pro吗',
answer: '可以。Max用户购买Pro套餐后系统会在当前Max订阅到期后自动切换到Pro版本并从到期日开始计算Pro的订阅时长。在Max到期前您仍可继续使用Max的全部功能。',
},
{
question: '是否支持退款?',