update pay ui

This commit is contained in:
2025-12-12 14:04:11 +08:00
parent 2a4e2a41ec
commit 39c6eacb58

View File

@@ -36,7 +36,9 @@ import {
FaTimes, FaTimes,
FaChevronDown, FaChevronDown,
FaChevronUp, FaChevronUp,
FaExternalLinkAlt,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { AlipayCircleOutlined } from '@ant-design/icons';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
import { useAuth } from '../../contexts/AuthContext'; import { useAuth } from '../../contexts/AuthContext';
@@ -147,6 +149,7 @@ export default function SubscriptionContentNew() {
const [paymentCountdown, setPaymentCountdown] = useState(300); const [paymentCountdown, setPaymentCountdown] = useState(300);
const [autoCheckInterval, setAutoCheckInterval] = useState(null); const [autoCheckInterval, setAutoCheckInterval] = useState(null);
const [forceUpdating, setForceUpdating] = useState(false); const [forceUpdating, setForceUpdating] = useState(false);
const [paymentMethod, setPaymentMethod] = useState<'wechat' | 'alipay'>('wechat'); // 支付方式
const [openFaqIndex, setOpenFaqIndex] = useState(null); const [openFaqIndex, setOpenFaqIndex] = useState(null);
@@ -392,14 +395,21 @@ export default function SubscriptionContentNew() {
} }
} }
const paymentMethodName = paymentMethod === 'alipay' ? 'alipay' : 'wechat_pay';
subscriptionEvents.trackPaymentInitiated({ subscriptionEvents.trackPaymentInitiated({
planName: selectedPlan.name, planName: selectedPlan.name,
paymentMethod: 'wechat_pay', paymentMethod: paymentMethodName,
amount: price, amount: price,
billingCycle: selectedCycle, billingCycle: selectedCycle,
}); });
const response = await fetch('/api/payment/create-order', { // 根据支付方式选择不同的 API
const apiUrl = paymentMethod === 'alipay'
? '/api/payment/alipay/create-order'
: '/api/payment/create-order';
const response = await fetch(apiUrl, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -415,28 +425,54 @@ export default function SubscriptionContentNew() {
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
setPaymentOrder(data.data); if (paymentMethod === 'alipay') {
setPaymentCountdown(30 * 60); // 30分钟 // 支付宝:跳转到支付页面
startAutoPaymentCheck(data.data.id); if (data.data.pay_url) {
setPaymentOrder(data.data);
setPaymentCountdown(30 * 60);
startAutoPaymentCheck(data.data.id, 'alipay');
toast({ toast({
title: '订单创建成功', title: '订单创建成功',
description: '请使用微信扫描二维码完成支付', description: '正在跳转到支付宝支付页面...',
status: 'success', status: 'success',
duration: 3000, duration: 3000,
isClosable: true, isClosable: true,
}); });
// 延迟跳转,让用户看到提示
setTimeout(() => {
window.open(data.data.pay_url, '_blank');
}, 500);
} else {
throw new Error('支付链接获取失败');
}
} else {
// 微信:显示二维码
setPaymentOrder(data.data);
setPaymentCountdown(30 * 60);
startAutoPaymentCheck(data.data.id, 'wechat');
toast({
title: '订单创建成功',
description: '请使用微信扫描二维码完成支付',
status: 'success',
duration: 3000,
isClosable: true,
});
}
} else { } else {
throw new Error(data.message || '创建订单失败'); throw new Error(data.error || data.message || '创建订单失败');
} }
} else { } else {
throw new Error('网络请求失败'); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || '网络请求失败');
} }
} catch (error: any) { } catch (error: any) {
subscriptionEvents.trackPaymentFailed( subscriptionEvents.trackPaymentFailed(
{ {
planName: selectedPlan.name, planName: selectedPlan.name,
paymentMethod: 'wechat_pay', paymentMethod: paymentMethod === 'alipay' ? 'alipay' : 'wechat_pay',
amount: getCurrentPrice(selectedPlan), amount: getCurrentPrice(selectedPlan),
}, },
error.message error.message
@@ -454,25 +490,30 @@ export default function SubscriptionContentNew() {
} }
}; };
const startAutoPaymentCheck = (orderId: string) => { const startAutoPaymentCheck = (orderId: string, method: 'wechat' | 'alipay' = 'wechat') => {
// 根据支付方式选择不同的状态查询 API
const statusApiUrl = method === 'alipay'
? `/api/payment/alipay/order/${orderId}/status`
: `/api/payment/order/${orderId}/status`;
const checkInterval = setInterval(async () => { const checkInterval = setInterval(async () => {
try { try {
const response = await fetch(`/api/payment/order/${orderId}/status`, { const response = await fetch(statusApiUrl, {
credentials: 'include', credentials: 'include',
}); });
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
if (data.success && data.data.status === 'paid') { if (data.success && (data.data.status === 'paid' || data.payment_success)) {
clearInterval(checkInterval); clearInterval(checkInterval);
setAutoCheckInterval(null); setAutoCheckInterval(null);
subscriptionEvents.trackPaymentSuccessful({ subscriptionEvents.trackPaymentSuccessful({
planName: selectedPlan?.name, planName: selectedPlan?.name,
paymentMethod: 'wechat_pay', paymentMethod: method === 'alipay' ? 'alipay' : 'wechat_pay',
amount: paymentOrder?.amount, amount: paymentOrder?.amount,
orderId: orderId, orderId: orderId,
transactionId: data.data.transaction_id, transactionId: data.data.transaction_id || data.data.alipay_trade_no,
}); });
toast({ toast({
@@ -500,13 +541,19 @@ export default function SubscriptionContentNew() {
setForceUpdating(true); setForceUpdating(true);
try { try {
const response = await fetch(`/api/payment/order/${paymentOrder.order_id}/status`, { // 根据订单的支付方式选择不同的查询 API
const orderPaymentMethod = (paymentOrder as any).payment_method || paymentMethod;
const statusApiUrl = orderPaymentMethod === 'alipay'
? `/api/payment/alipay/order/${(paymentOrder as any).id}/status`
: `/api/payment/order/${(paymentOrder as any).id}/status`;
const response = await fetch(statusApiUrl, {
credentials: 'include', credentials: 'include',
}); });
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
if (data.success && data.data.status === 'paid') { if (data.success && (data.data.status === 'paid' || data.payment_success)) {
toast({ toast({
title: '支付成功!', title: '支付成功!',
description: '您的订阅已激活', description: '您的订阅已激活',
@@ -540,6 +587,13 @@ export default function SubscriptionContentNew() {
} }
}; };
// 重新打开支付宝支付页面
const handleReopenAlipay = () => {
if (paymentOrder && (paymentOrder as any).pay_url) {
window.open((paymentOrder as any).pay_url, '_blank');
}
};
// 合并数据库数据和前端配置 // 合并数据库数据和前端配置
const getMergedPlans = () => { const getMergedPlans = () => {
// 如果数据库还没有加载数据,使用静态配置 // 如果数据库还没有加载数据,使用静态配置
@@ -1176,11 +1230,60 @@ export default function SubscriptionContentNew() {
<Modal isOpen={isOpen} onClose={onClose} size="lg" isCentered> <Modal isOpen={isOpen} onClose={onClose} size="lg" isCentered>
<ModalOverlay bg="rgba(0, 0, 0, 0.8)" backdropFilter="blur(10px)" /> <ModalOverlay bg="rgba(0, 0, 0, 0.8)" backdropFilter="blur(10px)" />
<ModalContent bg="#1e1e1e" borderRadius="2xl" border="1px solid rgba(255, 255, 255, 0.1)"> <ModalContent bg="#1e1e1e" borderRadius="2xl" border="1px solid rgba(255, 255, 255, 0.1)">
<ModalHeader color="white"></ModalHeader> <ModalHeader color="white">
{paymentMethod === 'alipay' ? '支付宝支付' : '微信支付'}
</ModalHeader>
<ModalCloseButton color="white" /> <ModalCloseButton color="white" />
<ModalBody pb={6}> <ModalBody pb={6}>
{!paymentOrder ? ( {!paymentOrder ? (
<VStack spacing={6} align="stretch"> <VStack spacing={6} align="stretch">
{/* 支付方式选择 */}
<Box>
<Text color="rgba(255, 255, 255, 0.7)" fontSize="sm" mb={3}>
</Text>
<HStack spacing={3}>
<Button
flex={1}
h="60px"
bg={paymentMethod === 'wechat' ? 'rgba(7, 193, 96, 0.15)' : 'rgba(255, 255, 255, 0.05)'}
border="2px solid"
borderColor={paymentMethod === 'wechat' ? '#07C160' : 'rgba(255, 255, 255, 0.1)'}
borderRadius="xl"
onClick={() => setPaymentMethod('wechat')}
_hover={{
borderColor: paymentMethod === 'wechat' ? '#07C160' : 'rgba(255, 255, 255, 0.3)',
bg: paymentMethod === 'wechat' ? 'rgba(7, 193, 96, 0.2)' : 'rgba(255, 255, 255, 0.08)',
}}
transition="all 0.2s"
>
<HStack spacing={3}>
<Icon as={FaWeixin} color="#07C160" boxSize={6} />
<Text color="white" fontWeight="medium"></Text>
</HStack>
</Button>
<Button
flex={1}
h="60px"
bg={paymentMethod === 'alipay' ? 'rgba(22, 119, 255, 0.15)' : 'rgba(255, 255, 255, 0.05)'}
border="2px solid"
borderColor={paymentMethod === 'alipay' ? '#1677FF' : 'rgba(255, 255, 255, 0.1)'}
borderRadius="xl"
onClick={() => setPaymentMethod('alipay')}
_hover={{
borderColor: paymentMethod === 'alipay' ? '#1677FF' : 'rgba(255, 255, 255, 0.3)',
bg: paymentMethod === 'alipay' ? 'rgba(22, 119, 255, 0.2)' : 'rgba(255, 255, 255, 0.08)',
}}
transition="all 0.2s"
>
<HStack spacing={3}>
<Box as={AlipayCircleOutlined} fontSize="24px" color="#1677FF" />
<Text color="white" fontWeight="medium"></Text>
</HStack>
</Button>
</HStack>
</Box>
{/* 订阅类型提示 */} {/* 订阅类型提示 */}
{selectedPlan && priceInfo && ( {selectedPlan && priceInfo && (
<> <>
@@ -1357,26 +1460,67 @@ export default function SubscriptionContentNew() {
<Button <Button
w="full" w="full"
size="lg" size="lg"
bgGradient="linear-gradient(135deg, #D4AF37, #B8941F)" bgGradient={paymentMethod === 'alipay'
color="#000" ? 'linear-gradient(135deg, #1677FF, #0958d9)'
: 'linear-gradient(135deg, #07C160, #059048)'}
color="white"
fontWeight="bold" fontWeight="bold"
onClick={handleCreatePaymentOrder} onClick={handleCreatePaymentOrder}
isLoading={loading} isLoading={loading}
isDisabled={!selectedPlan} isDisabled={!selectedPlan}
leftIcon={paymentMethod === 'alipay'
? <Box as={AlipayCircleOutlined} fontSize="20px" />
: <Icon as={FaWeixin} boxSize={5} />}
_hover={{ _hover={{
bgGradient: 'linear-gradient(135deg, #F4E3A7, #D4AF37)', bgGradient: paymentMethod === 'alipay'
? 'linear-gradient(135deg, #4096ff, #1677FF)'
: 'linear-gradient(135deg, #10d76e, #07C160)',
transform: 'translateY(-2px)',
boxShadow: paymentMethod === 'alipay'
? '0 8px 25px rgba(22, 119, 255, 0.4)'
: '0 8px 25px rgba(7, 193, 96, 0.4)',
}} }}
transition="all 0.3s"
> >
{priceInfo?.is_upgrade && priceInfo?.final_amount === 0 {priceInfo?.is_upgrade && priceInfo?.final_amount === 0
? '立即免费升级' ? '立即免费升级'
: '创建微信支付订单'} : paymentMethod === 'alipay'
? '支付宝支付'
: '微信扫码支付'}
</Button> </Button>
</VStack> </VStack>
) : ( ) : (
<VStack spacing={4}> <VStack spacing={4}>
<Text color="rgba(255, 255, 255, 0.7)" fontSize="lg" fontWeight="bold"> {/* 根据支付方式显示不同提示 */}
使 {(paymentOrder as any).payment_method === 'alipay' ? (
</Text> <>
<Box
p={4}
bg="rgba(22, 119, 255, 0.1)"
borderRadius="lg"
border="1px solid rgba(22, 119, 255, 0.3)"
w="full"
textAlign="center"
>
<HStack justify="center" spacing={2} mb={2}>
<Box as={AlipayCircleOutlined} fontSize="24px" color="#1677FF" />
<Text color="#1677FF" fontSize="lg" fontWeight="bold">
</Text>
</HStack>
<Text color="rgba(255, 255, 255, 0.7)" fontSize="sm">
</Text>
<Text color="rgba(255, 255, 255, 0.5)" fontSize="xs" mt={1}>
</Text>
</Box>
</>
) : (
<Text color="rgba(255, 255, 255, 0.7)" fontSize="lg" fontWeight="bold">
使
</Text>
)}
{/* 倒计时 */} {/* 倒计时 */}
<Box <Box
@@ -1389,7 +1533,7 @@ export default function SubscriptionContentNew() {
<HStack justify="center" spacing={2} mb={2}> <HStack justify="center" spacing={2} mb={2}>
<Icon as={FaClock} color="orange.400" /> <Icon as={FaClock} color="orange.400" />
<Text color="orange.300" fontSize="sm"> <Text color="orange.300" fontSize="sm">
: {formatTime(paymentCountdown)} : {formatTime(paymentCountdown)}
</Text> </Text>
</HStack> </HStack>
<Progress <Progress
@@ -1400,36 +1544,38 @@ export default function SubscriptionContentNew() {
/> />
</Box> </Box>
{/* 二维码 */} {/* 微信二维码(仅微信支付显示) */}
<Box textAlign="center"> {(paymentOrder as any).payment_method !== 'alipay' && (
{paymentOrder.qr_code_url ? ( <Box textAlign="center">
<Image {(paymentOrder as any).qr_code_url ? (
src={paymentOrder.qr_code_url} <Image
alt="微信支付二维码" src={(paymentOrder as any).qr_code_url}
mx="auto" alt="微信支付二维码"
maxW="200px" mx="auto"
border="2px solid" maxW="200px"
borderColor="rgba(255, 255, 255, 0.2)" border="2px solid"
borderRadius="lg" borderColor="rgba(255, 255, 255, 0.2)"
bg="white" borderRadius="lg"
p={2} bg="white"
/> p={2}
) : ( />
<Flex ) : (
w="200px" <Flex
h="200px" w="200px"
mx="auto" h="200px"
bg="rgba(255, 255, 255, 0.05)" mx="auto"
alignItems="center" bg="rgba(255, 255, 255, 0.05)"
justifyContent="center" alignItems="center"
border="2px solid" justifyContent="center"
borderColor="rgba(255, 255, 255, 0.2)" border="2px solid"
borderRadius="lg" borderColor="rgba(255, 255, 255, 0.2)"
> borderRadius="lg"
<Icon as={FaQrcode} color="rgba(255, 255, 255, 0.3)" boxSize={12} /> >
</Flex> <Icon as={FaQrcode} color="rgba(255, 255, 255, 0.3)" boxSize={12} />
)} </Flex>
</Box> )}
</Box>
)}
{/* 订单信息 */} {/* 订单信息 */}
<Box <Box
@@ -1440,18 +1586,39 @@ export default function SubscriptionContentNew() {
w="full" w="full"
> >
<Text fontSize="xs" color="rgba(255, 255, 255, 0.5)" mb={2}> <Text fontSize="xs" color="rgba(255, 255, 255, 0.5)" mb={2}>
: {paymentOrder.order_no} : {(paymentOrder as any).order_no}
</Text> </Text>
<Flex justify="space-between" align="baseline"> <Flex justify="space-between" align="baseline">
<Text color="rgba(255, 255, 255, 0.7)">:</Text> <Text color="rgba(255, 255, 255, 0.7)">:</Text>
<Text fontSize="xl" fontWeight="bold" color="#D4AF37"> <Text fontSize="xl" fontWeight="bold" color="#D4AF37">
¥{paymentOrder.amount} ¥{(paymentOrder as any).amount}
</Text> </Text>
</Flex> </Flex>
</Box> </Box>
{/* 操作按钮 */} {/* 操作按钮 */}
<VStack spacing={3} w="full"> <VStack spacing={3} w="full">
{/* 支付宝:重新打开支付页面按钮 */}
{(paymentOrder as any).payment_method === 'alipay' && (paymentOrder as any).pay_url && (
<Button
w="full"
size="lg"
bgGradient="linear-gradient(135deg, #1677FF, #0958d9)"
color="white"
fontWeight="bold"
leftIcon={<Icon as={FaExternalLinkAlt} />}
onClick={handleReopenAlipay}
_hover={{
bgGradient: 'linear-gradient(135deg, #4096ff, #1677FF)',
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(22, 119, 255, 0.4)',
}}
transition="all 0.3s"
>
</Button>
)}
<Button <Button
w="full" w="full"
size="lg" size="lg"