feat: add stripe topup page

This commit is contained in:
wzxjohn
2025-07-10 20:28:29 +08:00
parent 3568042cd9
commit e8ae980104
2 changed files with 220 additions and 2 deletions

View File

@@ -28,7 +28,6 @@ var stripeAdaptor = &StripeAdaptor{}
type StripePayRequest struct {
Amount int64 `json:"amount"`
PaymentMethod string `json:"payment_method"`
TopUpCode string `json:"top_up_code"`
}
type StripeAdaptor struct {

View File

@@ -59,6 +59,13 @@ const TopUp = () => {
statusState?.status?.enable_online_topup || false,
);
const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
const [stripeAmount, setStripeAmount] = useState(0.0);
const [stripeMinTopUp, setStripeMinTopUp] = useState(statusState?.status?.stripe_min_topup || 1);
const [stripeTopUpCount, setStripeTopUpCount] = useState(statusState?.status?.stripe_min_topup || 1);
const [enableStripeTopUp, setEnableStripeTopUp] = useState(statusState?.status?.enable_stripe_topup || false);
const [stripeOpen, setStripeOpen] = useState(false);
const [userQuota, setUserQuota] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [open, setOpen] = useState(false);
@@ -231,6 +238,65 @@ const TopUp = () => {
}
};
const stripePreTopUp = async () => {
if (!enableStripeTopUp) {
showError(t('管理员未开启在线充值!'));
return;
}
setPayWay('stripe')
setPaymentLoading(true);
try {
await getStripeAmount();
if (stripeTopUpCount < stripeMinTopUp) {
showError(t('充值数量不能小于') + stripeMinTopUp);
return;
}
setStripeOpen(true);
} catch (error) {
showError(t('获取金额失败'));
} finally {
setPayWay('')
setPaymentLoading(false);
}
};
const onlineStripeTopUp = async () => {
if (stripeAmount === 0) {
await getStripeAmount();
}
if (stripeTopUpCount < stripeMinTopUp) {
showError(t('充值数量不能小于') + stripeMinTopUp);
return;
}
setConfirmLoading(true);
try {
const res = await API.post('/api/user/stripe/pay', {
amount: parseInt(stripeTopUpCount),
payment_method: 'stripe',
});
if (res !== undefined) {
const { message, data } = res.data;
if (message === 'success') {
processStripeCallback(data)
} else {
showError(data);
}
} else {
showError(res);
}
} catch (err) {
console.log(err);
showError(t('支付请求失败'));
} finally {
setStripeOpen(false);
setConfirmLoading(false);
}
}
const processStripeCallback = (data) => {
location.href = data.pay_link;
};
const getUserQuota = async () => {
setUserDataLoading(true);
let res = await API.get(`/api/user/self`);
@@ -327,6 +393,10 @@ const TopUp = () => {
setTopUpLink(statusState.status.top_up_link || '');
setEnableOnlineTopUp(statusState.status.enable_online_topup || false);
setPriceRatio(statusState.status.price || 1);
setStripeMinTopUp(statusState.status.stripe_min_topup || 1);
setStripeTopUpCount(statusState.status.stripe_min_topup || 1);
setEnableStripeTopUp(statusState.status.enable_stripe_topup || false);
}
}, [statusState?.status]);
@@ -334,6 +404,10 @@ const TopUp = () => {
return amount + ' ' + t('元');
};
const renderStripeAmount = () => {
return stripeAmount + ' ' + t('元');
};
const getAmount = async (value) => {
if (value === undefined) {
value = topUpCount;
@@ -361,10 +435,40 @@ const TopUp = () => {
setAmountLoading(false);
};
const getStripeAmount = async (value) => {
if (value === undefined) {
value = stripeTopUpCount
}
try {
const res = await API.post('/api/user/stripe/amount', {
amount: parseFloat(value),
});
if (res !== undefined) {
const { message, data } = res.data;
// showInfo(message);
if (message === 'success') {
setStripeAmount(parseFloat(data))
} else {
setStripeAmount( 0)
Toast.error({ content: '错误:' + data, id: 'getAmount' });
}
} else {
showError(res);
}
} catch (err) {
console.log(err);
} finally {
}
}
const handleCancel = () => {
setOpen(false);
};
const handleStripeCancel = () => {
setStripeOpen(false);
};
const handleTransferCancel = () => {
setOpenTransfer(false);
};
@@ -374,6 +478,9 @@ const TopUp = () => {
setTopUpCount(preset.value);
setSelectedPreset(preset.value);
setAmount(preset.value * priceRatio);
setStripeTopUpCount(preset.value);
setStripeAmount(preset.value);
};
// 格式化大数字显示
@@ -496,6 +603,25 @@ const TopUp = () => {
</div>
</Modal>
<Modal
title={t('确定要充值吗')}
visible={stripeOpen}
onOk={onlineStripeTopUp}
onCancel={handleStripeCancel}
maskClosable={false}
size='small'
centered
confirmLoading={confirmLoading}
>
<p>
{t('充值数量')}{stripeTopUpCount}
</p>
<p>
{t('实付金额')}{renderStripeAmount()}
</p>
<p>{t('是否确认充值?')}</p>
</Modal>
<div className='grid grid-cols-1 lg:grid-cols-12 gap-6'>
{/* 左侧充值区域 */}
<div className='lg:col-span-7 space-y-6 w-full'>
@@ -798,7 +924,7 @@ const TopUp = () => {
</>
)}
{!enableOnlineTopUp && (
{!enableOnlineTopUp && !enableStripeTopUp && (
<Banner
type='warning'
description={t(
@@ -809,6 +935,99 @@ const TopUp = () => {
/>
)}
{enableStripeTopUp && (
<>
{/* 桌面端显示的自定义金额和支付按钮 */}
<div className='hidden md:block space-y-4'>
{!enableOnlineTopUp?(
<Divider style={{ margin: '24px 0' }}>
<Text className='text-sm font-medium'>
{t('或输入自定义金额')}
</Text>
</Divider>
) : (
<Divider style={{ margin: '24px 0' }}>
<Text className='text-sm font-medium'>
{t('Stripe')}
</Text>
</Divider>
)
}
<div>
<div className='flex justify-between mb-2'>
<Text strong>{t('充值数量')}</Text>
{amountLoading ? (
<Skeleton.Title
style={{ width: '80px', height: '16px' }}
/>
) : (
<Text type='tertiary'>
{t('实付金额:') + renderStripeAmount()}
</Text>
)}
</div>
<InputNumber
disabled={!enableOnlineTopUp}
placeholder={
t('充值数量,最低 ') + renderQuotaWithAmount(stripeMinTopUp)
}
value={stripeTopUpCount}
min={stripeMinTopUp}
max={999999999}
step={1}
precision={0}
onChange={async (value) => {
if (value && value >= 1) {
setStripeTopUpCount(value);
setSelectedPreset(null);
await getStripeAmount(value);
}
}}
onBlur={(e) => {
const value = parseInt(e.target.value);
if (!value || value < 1) {
setStripeTopUpCount(1);
getStripeAmount(1);
}
}}
size='large'
className='w-full'
formatter={(value) => (value ? `${value}` : '')}
parser={(value) =>
value ? parseInt(value.replace(/[^\d]/g, '')) : 0
}
/>
</div>
<div>
<Text strong className='block mb-3'>
{t('选择支付方式')}
</Text>
<div className='grid grid-cols-1 gap-3'>
<Button
key='stripe'
type='primary'
onClick={() => stripePreTopUp()}
size='large'
disabled={!enableStripeTopUp}
loading={paymentLoading && payWay === 'stripe'}
icon={<CreditCard size={16} />}
style={{
height: '40px',
color: '#b161fe',
}}
className='transition-all hover:shadow-md w-full'
>
<span className='ml-1'>Stripe</span>
</Button>
</div>
</div>
</div>
</>
)}
<Divider style={{ margin: '24px 0' }}>
<Text className='text-sm font-medium'>{t('兑换码充值')}</Text>
</Divider>