mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 06:37:28 +00:00
Merge pull request #2838 from QuantumNous/fix/subscription-epay
✨ fix: Improve subscription payment handling and card layout consistency
This commit is contained in:
@@ -108,7 +108,7 @@ func SubscriptionRequestEpay(c *gin.Context) {
|
|||||||
common.ApiErrorMsg(c, "拉起支付失败")
|
common.ApiErrorMsg(c, "拉起支付失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
common.ApiSuccess(c, gin.H{"data": params, "url": uri})
|
c.JSON(http.StatusOK, gin.H{"message": "success", "data": params, "url": uri})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SubscriptionEpayNotify(c *gin.Context) {
|
func SubscriptionEpayNotify(c *gin.Context) {
|
||||||
|
|||||||
@@ -128,7 +128,11 @@ const SubscriptionPlansCard = ({
|
|||||||
showSuccess(t('已打开支付页面'));
|
showSuccess(t('已打开支付页面'));
|
||||||
closeBuy();
|
closeBuy();
|
||||||
} else {
|
} else {
|
||||||
showError(res.data?.data || res.data?.message || t('支付失败'));
|
const errorMsg =
|
||||||
|
typeof res.data?.data === 'string'
|
||||||
|
? res.data.data
|
||||||
|
: res.data?.message || t('支付失败');
|
||||||
|
showError(errorMsg);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(t('支付请求失败'));
|
showError(t('支付请求失败'));
|
||||||
@@ -152,7 +156,11 @@ const SubscriptionPlansCard = ({
|
|||||||
showSuccess(t('已打开支付页面'));
|
showSuccess(t('已打开支付页面'));
|
||||||
closeBuy();
|
closeBuy();
|
||||||
} else {
|
} else {
|
||||||
showError(res.data?.data || res.data?.message || t('支付失败'));
|
const errorMsg =
|
||||||
|
typeof res.data?.data === 'string'
|
||||||
|
? res.data.data
|
||||||
|
: res.data?.message || t('支付失败');
|
||||||
|
showError(errorMsg);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(t('支付请求失败'));
|
showError(t('支付请求失败'));
|
||||||
@@ -177,7 +185,11 @@ const SubscriptionPlansCard = ({
|
|||||||
showSuccess(t('已发起支付'));
|
showSuccess(t('已发起支付'));
|
||||||
closeBuy();
|
closeBuy();
|
||||||
} else {
|
} else {
|
||||||
showError(res.data?.data || res.data?.message || t('支付失败'));
|
const errorMsg =
|
||||||
|
typeof res.data?.data === 'string'
|
||||||
|
? res.data.data
|
||||||
|
: res.data?.message || t('支付失败');
|
||||||
|
showError(errorMsg);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(t('支付请求失败'));
|
showError(t('支付请求失败'));
|
||||||
@@ -269,9 +281,13 @@ const SubscriptionPlansCard = ({
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{/* 套餐列表骨架屏 */}
|
{/* 套餐列表骨架屏 */}
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
|
||||||
{[1, 2, 3].map((i) => (
|
{[1, 2, 3].map((i) => (
|
||||||
<Card key={i} className='!rounded-xl' bodyStyle={{ padding: 16 }}>
|
<Card
|
||||||
|
key={i}
|
||||||
|
className='!rounded-xl w-full h-full'
|
||||||
|
bodyStyle={{ padding: 16 }}
|
||||||
|
>
|
||||||
<Skeleton.Title
|
<Skeleton.Title
|
||||||
active
|
active
|
||||||
style={{ width: '60%', height: 24, marginBottom: 8 }}
|
style={{ width: '60%', height: 24, marginBottom: 8 }}
|
||||||
@@ -435,7 +451,7 @@ const SubscriptionPlansCard = ({
|
|||||||
|
|
||||||
{/* 可购买套餐 - 标准定价卡片 */}
|
{/* 可购买套餐 - 标准定价卡片 */}
|
||||||
{plans.length > 0 ? (
|
{plans.length > 0 ? (
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 gap-5 w-full'>
|
||||||
{plans.map((p, index) => {
|
{plans.map((p, index) => {
|
||||||
const plan = p?.plan;
|
const plan = p?.plan;
|
||||||
const totalAmount = Number(plan?.total_amount || 0);
|
const totalAmount = Number(plan?.total_amount || 0);
|
||||||
@@ -477,15 +493,15 @@ const SubscriptionPlansCard = ({
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={plan?.id}
|
key={plan?.id}
|
||||||
className={`!rounded-xl transition-all hover:shadow-lg ${
|
className={`!rounded-xl transition-all hover:shadow-lg w-full h-full ${
|
||||||
isPopular ? 'ring-2 ring-purple-500' : ''
|
isPopular ? 'ring-2 ring-purple-500' : ''
|
||||||
}`}
|
}`}
|
||||||
bodyStyle={{ padding: 0 }}
|
bodyStyle={{ padding: 0 }}
|
||||||
>
|
>
|
||||||
<div className='p-4'>
|
<div className='p-4 h-full flex flex-col'>
|
||||||
{/* 推荐标签 */}
|
{/* 推荐标签 */}
|
||||||
{isPopular && (
|
{isPopular && (
|
||||||
<div className='text-center mb-2'>
|
<div className='mb-2'>
|
||||||
<Tag color='purple' shape='circle' size='small'>
|
<Tag color='purple' shape='circle' size='small'>
|
||||||
<Sparkles size={10} className='mr-1' />
|
<Sparkles size={10} className='mr-1' />
|
||||||
{t('推荐')}
|
{t('推荐')}
|
||||||
@@ -493,7 +509,7 @@ const SubscriptionPlansCard = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 套餐名称 */}
|
{/* 套餐名称 */}
|
||||||
<div className='text-center mb-3'>
|
<div className='mb-3'>
|
||||||
<Typography.Title
|
<Typography.Title
|
||||||
heading={5}
|
heading={5}
|
||||||
ellipsis={{ rows: 1, showTooltip: true }}
|
ellipsis={{ rows: 1, showTooltip: true }}
|
||||||
@@ -514,8 +530,8 @@ const SubscriptionPlansCard = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 价格区域 */}
|
{/* 价格区域 */}
|
||||||
<div className='text-center py-2'>
|
<div className='py-2'>
|
||||||
<div className='flex items-baseline justify-center'>
|
<div className='flex items-baseline justify-start'>
|
||||||
<span className='text-xl font-bold text-purple-600'>
|
<span className='text-xl font-bold text-purple-600'>
|
||||||
{symbol}
|
{symbol}
|
||||||
</span>
|
</span>
|
||||||
@@ -526,7 +542,7 @@ const SubscriptionPlansCard = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 套餐权益描述 */}
|
{/* 套餐权益描述 */}
|
||||||
<div className='flex flex-col items-center gap-1 pb-2'>
|
<div className='flex flex-col items-start gap-1 pb-2'>
|
||||||
{planBenefits.map((item) => {
|
{planBenefits.map((item) => {
|
||||||
const content = (
|
const content = (
|
||||||
<div className='flex items-center gap-2 text-xs text-gray-500'>
|
<div className='flex items-center gap-2 text-xs text-gray-500'>
|
||||||
@@ -538,7 +554,7 @@ const SubscriptionPlansCard = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.label}
|
key={item.label}
|
||||||
className='w-full flex justify-center'
|
className='w-full flex justify-start'
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
@@ -546,7 +562,7 @@ const SubscriptionPlansCard = ({
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip key={item.label} content={item.tooltip}>
|
<Tooltip key={item.label} content={item.tooltip}>
|
||||||
<div className='w-full flex justify-center'>
|
<div className='w-full flex justify-start'>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -554,36 +570,38 @@ const SubscriptionPlansCard = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider margin={12} />
|
<div className='mt-auto'>
|
||||||
|
<Divider margin={12} />
|
||||||
|
|
||||||
{/* 购买按钮 */}
|
{/* 购买按钮 */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const count = getPlanPurchaseCount(p?.plan?.id);
|
const count = getPlanPurchaseCount(p?.plan?.id);
|
||||||
const reached = limit > 0 && count >= limit;
|
const reached = limit > 0 && count >= limit;
|
||||||
const tip = reached
|
const tip = reached
|
||||||
? t('已达到购买上限') + ` (${count}/${limit})`
|
? t('已达到购买上限') + ` (${count}/${limit})`
|
||||||
: '';
|
: '';
|
||||||
const buttonEl = (
|
const buttonEl = (
|
||||||
<Button
|
<Button
|
||||||
theme='outline'
|
theme='outline'
|
||||||
type='tertiary'
|
type='tertiary'
|
||||||
block
|
block
|
||||||
disabled={reached}
|
disabled={reached}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!reached) openBuy(p);
|
if (!reached) openBuy(p);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{reached ? t('已达上限') : t('立即订阅')}
|
{reached ? t('已达上限') : t('立即订阅')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
return reached ? (
|
return reached ? (
|
||||||
<Tooltip content={tip} position='top'>
|
<Tooltip content={tip} position='top'>
|
||||||
{buttonEl}
|
{buttonEl}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
buttonEl
|
buttonEl
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -249,7 +249,9 @@ const TopUp = () => {
|
|||||||
document.body.removeChild(form);
|
document.body.removeChild(form);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(data);
|
const errorMsg =
|
||||||
|
typeof data === 'string' ? data : message || t('支付失败');
|
||||||
|
showError(errorMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(res);
|
showError(res);
|
||||||
@@ -293,7 +295,9 @@ const TopUp = () => {
|
|||||||
if (message === 'success') {
|
if (message === 'success') {
|
||||||
processCreemCallback(data);
|
processCreemCallback(data);
|
||||||
} else {
|
} else {
|
||||||
showError(data);
|
const errorMsg =
|
||||||
|
typeof data === 'string' ? data : message || t('支付失败');
|
||||||
|
showError(errorMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showError(res);
|
showError(res);
|
||||||
|
|||||||
@@ -170,21 +170,21 @@ export const getModelCategories = (() => {
|
|||||||
gemini: {
|
gemini: {
|
||||||
label: 'Gemini',
|
label: 'Gemini',
|
||||||
icon: <Gemini.Color />,
|
icon: <Gemini.Color />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('gemini') ||
|
model.model_name.toLowerCase().includes('gemini') ||
|
||||||
model.model_name.toLowerCase().includes('gemma') ||
|
model.model_name.toLowerCase().includes('gemma') ||
|
||||||
model.model_name.toLowerCase().includes('learnlm') ||
|
model.model_name.toLowerCase().includes('learnlm') ||
|
||||||
model.model_name.toLowerCase().startsWith('embedding-') ||
|
model.model_name.toLowerCase().startsWith('embedding-') ||
|
||||||
model.model_name.toLowerCase().includes('text-embedding-004') ||
|
model.model_name.toLowerCase().includes('text-embedding-004') ||
|
||||||
model.model_name.toLowerCase().includes('imagen-4') ||
|
model.model_name.toLowerCase().includes('imagen-4') ||
|
||||||
model.model_name.toLowerCase().includes('veo-') ||
|
model.model_name.toLowerCase().includes('veo-') ||
|
||||||
model.model_name.toLowerCase().includes('aqa') ,
|
model.model_name.toLowerCase().includes('aqa'),
|
||||||
},
|
},
|
||||||
moonshot: {
|
moonshot: {
|
||||||
label: 'Moonshot',
|
label: 'Moonshot',
|
||||||
icon: <Moonshot />,
|
icon: <Moonshot />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('moonshot') ||
|
model.model_name.toLowerCase().includes('moonshot') ||
|
||||||
model.model_name.toLowerCase().includes('kimi'),
|
model.model_name.toLowerCase().includes('kimi'),
|
||||||
},
|
},
|
||||||
zhipu: {
|
zhipu: {
|
||||||
@@ -192,8 +192,8 @@ export const getModelCategories = (() => {
|
|||||||
icon: <Zhipu.Color />,
|
icon: <Zhipu.Color />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('chatglm') ||
|
model.model_name.toLowerCase().includes('chatglm') ||
|
||||||
model.model_name.toLowerCase().includes('glm-') ||
|
model.model_name.toLowerCase().includes('glm-') ||
|
||||||
model.model_name.toLowerCase().includes('cogview') ||
|
model.model_name.toLowerCase().includes('cogview') ||
|
||||||
model.model_name.toLowerCase().includes('cogvideo'),
|
model.model_name.toLowerCase().includes('cogvideo'),
|
||||||
},
|
},
|
||||||
qwen: {
|
qwen: {
|
||||||
@@ -209,8 +209,8 @@ export const getModelCategories = (() => {
|
|||||||
minimax: {
|
minimax: {
|
||||||
label: 'MiniMax',
|
label: 'MiniMax',
|
||||||
icon: <Minimax.Color />,
|
icon: <Minimax.Color />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('abab') ||
|
model.model_name.toLowerCase().includes('abab') ||
|
||||||
model.model_name.toLowerCase().includes('minimax'),
|
model.model_name.toLowerCase().includes('minimax'),
|
||||||
},
|
},
|
||||||
baidu: {
|
baidu: {
|
||||||
@@ -236,7 +236,7 @@ export const getModelCategories = (() => {
|
|||||||
cohere: {
|
cohere: {
|
||||||
label: 'Cohere',
|
label: 'Cohere',
|
||||||
icon: <Cohere.Color />,
|
icon: <Cohere.Color />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('command') ||
|
model.model_name.toLowerCase().includes('command') ||
|
||||||
model.model_name.toLowerCase().includes('c4ai-') ||
|
model.model_name.toLowerCase().includes('c4ai-') ||
|
||||||
model.model_name.toLowerCase().includes('embed-'),
|
model.model_name.toLowerCase().includes('embed-'),
|
||||||
@@ -259,7 +259,7 @@ export const getModelCategories = (() => {
|
|||||||
mistral: {
|
mistral: {
|
||||||
label: 'Mistral AI',
|
label: 'Mistral AI',
|
||||||
icon: <Mistral.Color />,
|
icon: <Mistral.Color />,
|
||||||
filter: (model) =>
|
filter: (model) =>
|
||||||
model.model_name.toLowerCase().includes('mistral') ||
|
model.model_name.toLowerCase().includes('mistral') ||
|
||||||
model.model_name.toLowerCase().includes('codestral') ||
|
model.model_name.toLowerCase().includes('codestral') ||
|
||||||
model.model_name.toLowerCase().includes('pixtral') ||
|
model.model_name.toLowerCase().includes('pixtral') ||
|
||||||
|
|||||||
Reference in New Issue
Block a user