Add a lightweight active-subscription check to skip subscription pre-consume when none exist, reducing unnecessary transactions and locks. In the subscription UI, disable subscription-first options when no active plan is available, show the effective fallback to wallet with a clear notice, and distinguish “invalidated” from “expired” states. Update i18n strings across supported locales to reflect the new messages and status labels.
685 lines
25 KiB
JavaScript
685 lines
25 KiB
JavaScript
/*
|
||
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 <https://www.gnu.org/licenses/>.
|
||
|
||
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 disableSubscriptionPreference = !hasActiveSubscription;
|
||
const isSubscriptionPreference =
|
||
billingPreference === 'subscription_first' ||
|
||
billingPreference === 'subscription_only';
|
||
const displayBillingPreference =
|
||
disableSubscriptionPreference && isSubscriptionPreference
|
||
? 'wallet_first'
|
||
: billingPreference;
|
||
const subscriptionPreferenceLabel =
|
||
billingPreference === 'subscription_only' ? t('仅用订阅') : t('优先订阅');
|
||
|
||
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 ? (
|
||
<div className='space-y-4'>
|
||
{/* 我的订阅骨架屏 */}
|
||
<Card className='!rounded-xl w-full' bodyStyle={{ padding: '12px' }}>
|
||
<div className='flex items-center justify-between mb-3'>
|
||
<Skeleton.Title active style={{ width: 100, height: 20 }} />
|
||
<Skeleton.Button active style={{ width: 24, height: 24 }} />
|
||
</div>
|
||
<div className='space-y-2'>
|
||
<Skeleton.Paragraph active rows={2} />
|
||
</div>
|
||
</Card>
|
||
{/* 套餐列表骨架屏 */}
|
||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full px-1'>
|
||
{[1, 2, 3].map((i) => (
|
||
<Card
|
||
key={i}
|
||
className='!rounded-xl w-full h-full'
|
||
bodyStyle={{ padding: 16 }}
|
||
>
|
||
<Skeleton.Title
|
||
active
|
||
style={{ width: '60%', height: 24, marginBottom: 8 }}
|
||
/>
|
||
<Skeleton.Paragraph
|
||
active
|
||
rows={1}
|
||
style={{ marginBottom: 12 }}
|
||
/>
|
||
<div className='text-center py-4'>
|
||
<Skeleton.Title
|
||
active
|
||
style={{ width: '40%', height: 32, margin: '0 auto' }}
|
||
/>
|
||
</div>
|
||
<Skeleton.Paragraph active rows={3} style={{ marginTop: 12 }} />
|
||
<Skeleton.Button
|
||
active
|
||
block
|
||
style={{ marginTop: 16, height: 32 }}
|
||
/>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<Space vertical style={{ width: '100%' }} spacing={8}>
|
||
{/* 当前订阅状态 */}
|
||
<Card className='!rounded-xl w-full' bodyStyle={{ padding: '12px' }}>
|
||
<div className='flex items-center justify-between mb-2 gap-3'>
|
||
<div className='flex items-center gap-2 flex-1 min-w-0'>
|
||
<Text strong>{t('我的订阅')}</Text>
|
||
{hasActiveSubscription ? (
|
||
<Tag
|
||
color='white'
|
||
size='small'
|
||
shape='circle'
|
||
prefixIcon={<Badge dot type='success' />}
|
||
>
|
||
{activeSubscriptions.length} {t('个生效中')}
|
||
</Tag>
|
||
) : (
|
||
<Tag color='white' size='small' shape='circle'>
|
||
{t('无生效')}
|
||
</Tag>
|
||
)}
|
||
{allSubscriptions.length > activeSubscriptions.length && (
|
||
<Tag color='white' size='small' shape='circle'>
|
||
{allSubscriptions.length - activeSubscriptions.length}{' '}
|
||
{t('个已过期')}
|
||
</Tag>
|
||
)}
|
||
</div>
|
||
<div className='flex items-center gap-2'>
|
||
<Select
|
||
value={displayBillingPreference}
|
||
onChange={onChangeBillingPreference}
|
||
size='small'
|
||
optionList={[
|
||
{
|
||
value: 'subscription_first',
|
||
label: disableSubscriptionPreference
|
||
? `${t('优先订阅')} (${t('无生效')})`
|
||
: t('优先订阅'),
|
||
disabled: disableSubscriptionPreference,
|
||
},
|
||
{ value: 'wallet_first', label: t('优先钱包') },
|
||
{
|
||
value: 'subscription_only',
|
||
label: disableSubscriptionPreference
|
||
? `${t('仅用订阅')} (${t('无生效')})`
|
||
: t('仅用订阅'),
|
||
disabled: disableSubscriptionPreference,
|
||
},
|
||
{ value: 'wallet_only', label: t('仅用钱包') },
|
||
]}
|
||
/>
|
||
<Button
|
||
size='small'
|
||
theme='light'
|
||
type='tertiary'
|
||
icon={
|
||
<RefreshCw
|
||
size={12}
|
||
className={refreshing ? 'animate-spin' : ''}
|
||
/>
|
||
}
|
||
onClick={handleRefresh}
|
||
loading={refreshing}
|
||
/>
|
||
</div>
|
||
</div>
|
||
{disableSubscriptionPreference && isSubscriptionPreference && (
|
||
<Text type='tertiary' size='small'>
|
||
{t('已保存偏好为')}
|
||
{subscriptionPreferenceLabel}
|
||
{t(',当前无生效订阅,将自动使用钱包')}
|
||
</Text>
|
||
)}
|
||
|
||
{hasAnySubscription ? (
|
||
<>
|
||
<Divider margin={8} />
|
||
<div className='max-h-64 overflow-y-auto pr-1 semi-table-body'>
|
||
{allSubscriptions.map((sub, subIndex) => {
|
||
const isLast = subIndex === allSubscriptions.length - 1;
|
||
const subscription = sub.subscription;
|
||
const totalAmount = Number(subscription?.amount_total || 0);
|
||
const usedAmount = Number(subscription?.amount_used || 0);
|
||
const remainAmount =
|
||
totalAmount > 0
|
||
? Math.max(0, totalAmount - usedAmount)
|
||
: 0;
|
||
const planTitle =
|
||
planTitleMap.get(subscription?.plan_id) || '';
|
||
const remainDays = getRemainingDays(sub);
|
||
const usagePercent = getUsagePercent(sub);
|
||
const now = Date.now() / 1000;
|
||
const isExpired = (subscription?.end_time || 0) < now;
|
||
const isCancelled = subscription?.status === 'cancelled';
|
||
const isActive =
|
||
subscription?.status === 'active' && !isExpired;
|
||
|
||
return (
|
||
<div key={subscription?.id || subIndex}>
|
||
{/* 订阅概要 */}
|
||
<div className='flex items-center justify-between text-xs mb-2'>
|
||
<div className='flex items-center gap-2'>
|
||
<span className='font-medium'>
|
||
{planTitle
|
||
? `${planTitle} · ${t('订阅')} #${subscription?.id}`
|
||
: `${t('订阅')} #${subscription?.id}`}
|
||
</span>
|
||
{isActive ? (
|
||
<Tag
|
||
color='white'
|
||
size='small'
|
||
shape='circle'
|
||
prefixIcon={<Badge dot type='success' />}
|
||
>
|
||
{t('生效')}
|
||
</Tag>
|
||
) : isCancelled ? (
|
||
<Tag color='white' size='small' shape='circle'>
|
||
{t('已作废')}
|
||
</Tag>
|
||
) : (
|
||
<Tag color='white' size='small' shape='circle'>
|
||
{t('已过期')}
|
||
</Tag>
|
||
)}
|
||
</div>
|
||
{isActive && (
|
||
<span className='text-gray-500'>
|
||
{t('剩余')} {remainDays} {t('天')}
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div className='text-xs text-gray-500 mb-2'>
|
||
{isActive
|
||
? t('至')
|
||
: isCancelled
|
||
? t('作废于')
|
||
: t('过期于')}{' '}
|
||
{new Date(
|
||
(subscription?.end_time || 0) * 1000,
|
||
).toLocaleString()}
|
||
</div>
|
||
<div className='text-xs text-gray-500 mb-2'>
|
||
{t('总额度')}:{' '}
|
||
{totalAmount > 0 ? (
|
||
<Tooltip
|
||
content={`${t('原生额度')}:${usedAmount}/${totalAmount} · ${t('剩余')} ${remainAmount}`}
|
||
>
|
||
<span>
|
||
{renderQuota(usedAmount)}/
|
||
{renderQuota(totalAmount)} · {t('剩余')}{' '}
|
||
{renderQuota(remainAmount)}
|
||
</span>
|
||
</Tooltip>
|
||
) : (
|
||
t('不限')
|
||
)}
|
||
{totalAmount > 0 && (
|
||
<span className='ml-2'>
|
||
{t('已用')} {usagePercent}%
|
||
</span>
|
||
)}
|
||
</div>
|
||
{!isLast && <Divider margin={12} />}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div className='text-xs text-gray-500'>
|
||
{t('购买套餐后即可享受模型权益')}
|
||
</div>
|
||
)}
|
||
</Card>
|
||
|
||
{/* 可购买套餐 - 标准定价卡片 */}
|
||
{plans.length > 0 ? (
|
||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full px-1'>
|
||
{plans.map((p, index) => {
|
||
const plan = p?.plan;
|
||
const totalAmount = Number(plan?.total_amount || 0);
|
||
const { symbol, rate } = getCurrencyConfig();
|
||
const price = Number(plan?.price_amount || 0);
|
||
const convertedPrice = price * rate;
|
||
const displayPrice = convertedPrice.toFixed(
|
||
Number.isInteger(convertedPrice) ? 0 : 2,
|
||
);
|
||
const isPopular = index === 0 && plans.length > 1;
|
||
const limit = Number(plan?.max_purchase_per_user || 0);
|
||
const limitLabel = limit > 0 ? `${t('限购')} ${limit}` : null;
|
||
const totalLabel =
|
||
totalAmount > 0
|
||
? `${t('总额度')}: ${renderQuota(totalAmount)}`
|
||
: `${t('总额度')}: ${t('不限')}`;
|
||
const upgradeLabel = plan?.upgrade_group
|
||
? `${t('升级分组')}: ${plan.upgrade_group}`
|
||
: null;
|
||
const resetLabel =
|
||
formatSubscriptionResetPeriod(plan, t) === t('不重置')
|
||
? null
|
||
: `${t('额度重置')}: ${formatSubscriptionResetPeriod(plan, t)}`;
|
||
const planBenefits = [
|
||
{
|
||
label: `${t('有效期')}: ${formatSubscriptionDuration(plan, t)}`,
|
||
},
|
||
resetLabel ? { label: resetLabel } : null,
|
||
totalAmount > 0
|
||
? {
|
||
label: totalLabel,
|
||
tooltip: `${t('原生额度')}:${totalAmount}`,
|
||
}
|
||
: { label: totalLabel },
|
||
limitLabel ? { label: limitLabel } : null,
|
||
upgradeLabel ? { label: upgradeLabel } : null,
|
||
].filter(Boolean);
|
||
|
||
return (
|
||
<Card
|
||
key={plan?.id}
|
||
className={`!rounded-xl transition-all hover:shadow-lg w-full h-full ${
|
||
isPopular ? 'ring-2 ring-purple-500' : ''
|
||
}`}
|
||
bodyStyle={{ padding: 0 }}
|
||
>
|
||
<div className='p-4 h-full flex flex-col'>
|
||
{/* 推荐标签 */}
|
||
{isPopular && (
|
||
<div className='mb-2'>
|
||
<Tag color='purple' shape='circle' size='small'>
|
||
<Sparkles size={10} className='mr-1' />
|
||
{t('推荐')}
|
||
</Tag>
|
||
</div>
|
||
)}
|
||
{/* 套餐名称 */}
|
||
<div className='mb-3'>
|
||
<Typography.Title
|
||
heading={5}
|
||
ellipsis={{ rows: 1, showTooltip: true }}
|
||
style={{ margin: 0 }}
|
||
>
|
||
{plan?.title || t('订阅套餐')}
|
||
</Typography.Title>
|
||
{plan?.subtitle && (
|
||
<Text
|
||
type='tertiary'
|
||
size='small'
|
||
ellipsis={{ rows: 1, showTooltip: true }}
|
||
style={{ display: 'block' }}
|
||
>
|
||
{plan.subtitle}
|
||
</Text>
|
||
)}
|
||
</div>
|
||
|
||
{/* 价格区域 */}
|
||
<div className='py-2'>
|
||
<div className='flex items-baseline justify-start'>
|
||
<span className='text-xl font-bold text-purple-600'>
|
||
{symbol}
|
||
</span>
|
||
<span className='text-3xl font-bold text-purple-600'>
|
||
{displayPrice}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 套餐权益描述 */}
|
||
<div className='flex flex-col items-start gap-1 pb-2'>
|
||
{planBenefits.map((item) => {
|
||
const content = (
|
||
<div className='flex items-center gap-2 text-xs text-gray-500'>
|
||
<Badge dot type='tertiary' />
|
||
<span>{item.label}</span>
|
||
</div>
|
||
);
|
||
if (!item.tooltip) {
|
||
return (
|
||
<div
|
||
key={item.label}
|
||
className='w-full flex justify-start'
|
||
>
|
||
{content}
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<Tooltip key={item.label} content={item.tooltip}>
|
||
<div className='w-full flex justify-start'>
|
||
{content}
|
||
</div>
|
||
</Tooltip>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<div className='mt-auto'>
|
||
<Divider margin={12} />
|
||
|
||
{/* 购买按钮 */}
|
||
{(() => {
|
||
const count = getPlanPurchaseCount(p?.plan?.id);
|
||
const reached = limit > 0 && count >= limit;
|
||
const tip = reached
|
||
? t('已达到购买上限') + ` (${count}/${limit})`
|
||
: '';
|
||
const buttonEl = (
|
||
<Button
|
||
theme='outline'
|
||
type='primary'
|
||
block
|
||
disabled={reached}
|
||
onClick={() => {
|
||
if (!reached) openBuy(p);
|
||
}}
|
||
>
|
||
{reached ? t('已达上限') : t('立即订阅')}
|
||
</Button>
|
||
);
|
||
return reached ? (
|
||
<Tooltip content={tip} position='top'>
|
||
{buttonEl}
|
||
</Tooltip>
|
||
) : (
|
||
buttonEl
|
||
);
|
||
})()}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
);
|
||
})}
|
||
</div>
|
||
) : (
|
||
<div className='text-center text-gray-400 text-sm py-4'>
|
||
{t('暂无可购买套餐')}
|
||
</div>
|
||
)}
|
||
</Space>
|
||
)}
|
||
</>
|
||
);
|
||
|
||
return (
|
||
<>
|
||
{withCard ? (
|
||
<Card className='!rounded-2xl shadow-sm border-0'>{cardContent}</Card>
|
||
) : (
|
||
<div className='space-y-3'>{cardContent}</div>
|
||
)}
|
||
|
||
{/* 购买确认弹窗 */}
|
||
<SubscriptionPurchaseModal
|
||
t={t}
|
||
visible={open}
|
||
onCancel={closeBuy}
|
||
selectedPlan={selectedPlan}
|
||
paying={paying}
|
||
selectedEpayMethod={selectedEpayMethod}
|
||
setSelectedEpayMethod={setSelectedEpayMethod}
|
||
epayMethods={epayMethods}
|
||
enableOnlineTopUp={enableOnlineTopUp}
|
||
enableStripeTopUp={enableStripeTopUp}
|
||
enableCreemTopUp={enableCreemTopUp}
|
||
purchaseLimitInfo={
|
||
selectedPlan?.plan?.id
|
||
? {
|
||
limit: Number(selectedPlan?.plan?.max_purchase_per_user || 0),
|
||
count: getPlanPurchaseCount(selectedPlan?.plan?.id),
|
||
}
|
||
: null
|
||
}
|
||
onPayStripe={payStripe}
|
||
onPayCreem={payCreem}
|
||
onPayEpay={payEpay}
|
||
/>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default SubscriptionPlansCard;
|