/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useMemo, useState } from 'react'; import { Badge, Button, Card, Divider, Select, Skeleton, Space, Tag, Tooltip, Typography, } from '@douyinfe/semi-ui'; import { API, showError, showSuccess, renderQuota } from '../../helpers'; import { getCurrencyConfig } from '../../helpers/render'; import { RefreshCw, Sparkles } from 'lucide-react'; import SubscriptionPurchaseModal from './modals/SubscriptionPurchaseModal'; import { formatSubscriptionDuration, formatSubscriptionResetPeriod, } from '../../helpers/subscriptionFormat'; const { Text } = Typography; // 过滤易支付方式 function getEpayMethods(payMethods = []) { return (payMethods || []).filter( (m) => m?.type && m.type !== 'stripe' && m.type !== 'creem', ); } // 提交易支付表单 function submitEpayForm({ url, params }) { const form = document.createElement('form'); form.action = url; form.method = 'POST'; const isSafari = navigator.userAgent.indexOf('Safari') > -1 && navigator.userAgent.indexOf('Chrome') < 1; if (!isSafari) form.target = '_blank'; Object.keys(params || {}).forEach((key) => { const input = document.createElement('input'); input.type = 'hidden'; input.name = key; input.value = params[key]; form.appendChild(input); }); document.body.appendChild(form); form.submit(); document.body.removeChild(form); } const SubscriptionPlansCard = ({ t, loading = false, plans = [], payMethods = [], enableOnlineTopUp = false, enableStripeTopUp = false, enableCreemTopUp = false, billingPreference, onChangeBillingPreference, activeSubscriptions = [], allSubscriptions = [], reloadSubscriptionSelf, withCard = true, }) => { const [open, setOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); const [paying, setPaying] = useState(false); const [selectedEpayMethod, setSelectedEpayMethod] = useState(''); const [refreshing, setRefreshing] = useState(false); const epayMethods = useMemo(() => getEpayMethods(payMethods), [payMethods]); const openBuy = (p) => { setSelectedPlan(p); setSelectedEpayMethod(epayMethods?.[0]?.type || ''); setOpen(true); }; const closeBuy = () => { setOpen(false); setSelectedPlan(null); setPaying(false); }; const handleRefresh = async () => { setRefreshing(true); try { await reloadSubscriptionSelf?.(); } finally { setRefreshing(false); } }; const payStripe = async () => { if (!selectedPlan?.plan?.stripe_price_id) { showError(t('该套餐未配置 Stripe')); return; } setPaying(true); try { const res = await API.post('/api/subscription/stripe/pay', { plan_id: selectedPlan.plan.id, }); if (res.data?.message === 'success') { window.open(res.data.data?.pay_link, '_blank'); showSuccess(t('已打开支付页面')); closeBuy(); } else { const errorMsg = typeof res.data?.data === 'string' ? res.data.data : res.data?.message || t('支付失败'); showError(errorMsg); } } catch (e) { showError(t('支付请求失败')); } finally { setPaying(false); } }; const payCreem = async () => { if (!selectedPlan?.plan?.creem_product_id) { showError(t('该套餐未配置 Creem')); return; } setPaying(true); try { const res = await API.post('/api/subscription/creem/pay', { plan_id: selectedPlan.plan.id, }); if (res.data?.message === 'success') { window.open(res.data.data?.checkout_url, '_blank'); showSuccess(t('已打开支付页面')); closeBuy(); } else { const errorMsg = typeof res.data?.data === 'string' ? res.data.data : res.data?.message || t('支付失败'); showError(errorMsg); } } catch (e) { showError(t('支付请求失败')); } finally { setPaying(false); } }; const payEpay = async () => { if (!selectedEpayMethod) { showError(t('请选择支付方式')); return; } setPaying(true); try { const res = await API.post('/api/subscription/epay/pay', { plan_id: selectedPlan.plan.id, payment_method: selectedEpayMethod, }); if (res.data?.message === 'success') { submitEpayForm({ url: res.data.url, params: res.data.data }); showSuccess(t('已发起支付')); closeBuy(); } else { const errorMsg = typeof res.data?.data === 'string' ? res.data.data : res.data?.message || t('支付失败'); showError(errorMsg); } } catch (e) { showError(t('支付请求失败')); } finally { setPaying(false); } }; // 当前订阅信息 - 支持多个订阅 const hasActiveSubscription = activeSubscriptions.length > 0; const hasAnySubscription = allSubscriptions.length > 0; const planPurchaseCountMap = useMemo(() => { const map = new Map(); (allSubscriptions || []).forEach((sub) => { const planId = sub?.subscription?.plan_id; if (!planId) return; map.set(planId, (map.get(planId) || 0) + 1); }); return map; }, [allSubscriptions]); const planTitleMap = useMemo(() => { const map = new Map(); (plans || []).forEach((p) => { const plan = p?.plan; if (!plan?.id) return; map.set(plan.id, plan.title || ''); }); return map; }, [plans]); const getPlanPurchaseCount = (planId) => planPurchaseCountMap.get(planId) || 0; // 计算单个订阅的剩余天数 const getRemainingDays = (sub) => { if (!sub?.subscription?.end_time) return 0; const now = Date.now() / 1000; const remaining = sub.subscription.end_time - now; return Math.max(0, Math.ceil(remaining / 86400)); }; // 计算单个订阅的使用进度 const getUsagePercent = (sub) => { const total = Number(sub?.subscription?.amount_total || 0); const used = Number(sub?.subscription?.amount_used || 0); if (total <= 0) return 0; return Math.round((used / total) * 100); }; const cardContent = ( <> {/* 卡片头部 */} {loading ? (
{/* 我的订阅骨架屏 */}
{/* 套餐列表骨架屏 */}
{[1, 2, 3].map((i) => (
))}
) : ( {/* 当前订阅状态 */}
{t('我的订阅')} {hasActiveSubscription ? ( } > {activeSubscriptions.length} {t('个生效中')} ) : ( {t('无生效')} )} {allSubscriptions.length > activeSubscriptions.length && ( {allSubscriptions.length - activeSubscriptions.length}{' '} {t('个已过期')} )}