Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions controller/topup.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import (
)

func GetTopUpInfo(c *gin.Context) {
id := c.GetInt("id")
group, err := model.GetUserGroup(id, true)
if err != nil {
common.ApiError(c, err)
return
}
topupGroupRatio := common.GetTopupGroupRatio(group)

// 获取支付方式
payMethods := operation_setting.PayMethods

Expand Down Expand Up @@ -110,6 +118,7 @@ func GetTopUpInfo(c *gin.Context) {
"waffo_pancake_min_topup": setting.WaffoPancakeMinTopUp,
"amount_options": operation_setting.GetPaymentSetting().AmountOptions,
"discount": operation_setting.GetPaymentSetting().AmountDiscount,
"topup_group_ratio": topupGroupRatio,
"topup_link": common.TopUpLink,
}
common.ApiSuccess(c, data)
Expand Down
137 changes: 98 additions & 39 deletions web/classic/src/components/topup/RechargeCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,26 @@ const RechargeCard = ({
}) => {
const onlineFormApiRef = useRef(null);
const redeemFormApiRef = useRef(null);
const syncingPresetRef = useRef(false);
const initialTabSetRef = useRef(false);
const showAmountSkeleton = useMinimumLoadingTime(amountLoading);
const [activeTab, setActiveTab] = useState('topup');
const shouldShowSubscription =
!subscriptionLoading && subscriptionPlans.length > 0;
const regularPayMethods = payMethods || [];

const releasePresetSyncLock = () => {
if (typeof window !== 'undefined' && window.requestAnimationFrame) {
window.requestAnimationFrame(() => {
syncingPresetRef.current = false;
});
return;
}
setTimeout(() => {
syncingPresetRef.current = false;
}, 0);
};

useEffect(() => {
if (initialTabSetRef.current) return;
if (subscriptionLoading) return;
Expand All @@ -118,6 +131,11 @@ const RechargeCard = ({
setActiveTab('topup');
}
}, [shouldShowSubscription, activeTab]);

const currentTopupGroupRatio = Number(topupInfo?.topup_group_ratio) || 1;
const currentOriginalAmount =
Number(topUpCount || 0) * Number(priceRatio || 0) * currentTopupGroupRatio;

const topupContent = (
<Space vertical style={{ width: '100%' }}>
{/* 统计数据 */}
Expand Down Expand Up @@ -261,6 +279,9 @@ const RechargeCard = ({
step={1}
precision={0}
onChange={async (value) => {
if (syncingPresetRef.current) {
return;
}
if (value && value >= 1) {
setTopUpCount(value);
setSelectedPreset(null);
Expand Down Expand Up @@ -297,6 +318,16 @@ const RechargeCard = ({
<span style={{ color: 'red' }}>
{renderAmount()}
</span>
{currentOriginalAmount > 0 && (
<Text
type='tertiary'
style={{ marginLeft: 8, fontSize: '12px' }}
>
{t('原价')}:{currentOriginalAmount.toFixed(2)}
{' '}
{t('元')}
</Text>
)}
</Text>
</Skeleton>
}
Expand Down Expand Up @@ -406,6 +437,11 @@ const RechargeCard = ({
<span>{t('选择充值额度')}</span>
{(() => {
const { symbol, rate, type } = getCurrencyConfig();
const topupRate =
Number.isFinite(Number(priceRatio)) &&
Number(priceRatio) > 0
? Number(priceRatio)
: 1;
if (type === 'USD') return null;

return (
Expand All @@ -416,7 +452,11 @@ const RechargeCard = ({
fontWeight: 'normal',
}}
>
(1 $ = {rate.toFixed(2)} {symbol})
(1 $ =
{' '}
{(type === 'CNY' ? topupRate : rate).toFixed(2)}
{' '}
{symbol})
</span>
);
})()}
Expand All @@ -425,43 +465,50 @@ const RechargeCard = ({
>
<div className='grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2'>
{presetAmounts.map((preset, index) => {
const discount =
preset.discount ||
topupInfo?.discount?.[preset.value] ||
1.0;
const originalPrice = preset.value * priceRatio;
const presetValue = Number(preset.value);
const rawDiscount =
topupInfo?.discount?.[presetValue] ??
topupInfo?.discount?.[String(presetValue)] ??
preset.discount;
const discount = Number.isFinite(Number(rawDiscount))
? Number(rawDiscount)
: 1.0;
const topupGroupRatio =
Number(topupInfo?.topup_group_ratio) || 1;
const originalPrice =
presetValue * priceRatio * topupGroupRatio;
const discountedPrice = originalPrice * discount;
const hasDiscount = discount < 1.0;
const actualPay = discountedPrice;
const save = originalPrice - discountedPrice;

// 根据当前货币类型换算显示金额和数量
const { symbol, rate, type } = getCurrencyConfig();
const statusStr = localStorage.getItem('status');
let usdRate = 7; // 默认CNY汇率
try {
if (statusStr) {
const s = JSON.parse(statusStr);
usdRate = s?.usd_exchange_rate || 7;
}
} catch (e) {}
const topupRate =
Number.isFinite(Number(priceRatio)) &&
Number(priceRatio) > 0
? Number(priceRatio)
: 1;

let displayValue = preset.value; // 显示的数量
let displayValue = presetValue; // 显示的数量
let displayOriginalPay = originalPrice;
let displayActualPay = actualPay;
let displaySave = save;

if (type === 'USD') {
// 数量保持USD,价格从CNY转USD
displayActualPay = actualPay / usdRate;
displaySave = save / usdRate;
// 数量保持USD,价格从充值货币转回USD
displayOriginalPay = originalPrice / topupRate;
displayActualPay = actualPay / topupRate;
displaySave = save / topupRate;
} else if (type === 'CNY') {
// 数量转CNY,价格已是CNY
displayValue = preset.value * usdRate;
// 数量按充值价格折算为CNY
displayValue = presetValue * topupRate;
} else if (type === 'CUSTOM') {
// 数量和价格都转自定义货币
displayValue = preset.value * rate;
displayActualPay = (actualPay / usdRate) * rate;
displaySave = (save / usdRate) * rate;
displayValue = presetValue * rate;
displayOriginalPay = (originalPrice / topupRate) * rate;
displayActualPay = (actualPay / topupRate) * rate;
displaySave = (save / topupRate) * rate;
}

return (
Expand All @@ -470,19 +517,21 @@ const RechargeCard = ({
style={{
cursor: 'pointer',
border:
selectedPreset === preset.value
selectedPreset === presetValue
? '2px solid var(--semi-color-primary)'
: '1px solid var(--semi-color-border)',
height: '100%',
width: '100%',
}}
bodyStyle={{ padding: '12px' }}
onClick={() => {
selectPresetAmount(preset);
syncingPresetRef.current = true;
onlineFormApiRef.current?.setValue(
'topUpCount',
preset.value,
presetValue,
);
selectPresetAmount(preset);
releasePresetSyncLock();
}}
>
<div style={{ textAlign: 'center' }}>
Expand All @@ -492,6 +541,29 @@ const RechargeCard = ({
>
<Coins size={18} />
{formatLargeNumber(displayValue)} {symbol}
</Typography.Title>
<div
style={{
color: 'var(--semi-color-text-2)',
fontSize: '12px',
margin: '4px 0',
}}
>
{t('原价')} {symbol}
{displayOriginalPay.toFixed(2)}
</div>
<div
style={{
color: hasDiscount
? 'var(--semi-color-danger)'
: 'var(--semi-color-text-2)',
fontSize: '12px',
margin: '4px 0 0 0',
fontWeight: hasDiscount ? 600 : 400,
}}
>
{t('实付')} {symbol}
{displayActualPay.toFixed(2)}
{hasDiscount && (
<Tag style={{ marginLeft: 4 }} color='green'>
{t('折').includes('off')
Expand All @@ -503,19 +575,6 @@ const RechargeCard = ({
{t('折')}
</Tag>
)}
</Typography.Title>
<div
style={{
color: 'var(--semi-color-text-2)',
fontSize: '12px',
margin: '4px 0',
}}
>
{t('实付')} {symbol}
{displayActualPay.toFixed(2)},
{hasDiscount
? `${t('节省')} ${symbol}${displaySave.toFixed(2)}`
: `${t('节省')} ${symbol}0.00`}
</div>
</div>
</Card>
Expand Down
60 changes: 53 additions & 7 deletions web/classic/src/components/topup/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,40 @@ import TransferModal from './modals/TransferModal';
import PaymentConfirmModal from './modals/PaymentConfirmModal';
import TopupHistoryModal from './modals/TopupHistoryModal';

const parseDiscountMap = (data) => {
if (!data) {
return {};
}

let parsedData = data;
if (typeof data === 'string') {
try {
parsedData = JSON.parse(data);
} catch {
return {};
}
}

if (
!parsedData ||
typeof parsedData !== 'object' ||
Array.isArray(parsedData)
) {
return {};
}

return Object.entries(parsedData).reduce((result, [key, value]) => {
const numericKey = Number(key);
const numericValue = Number(value);

if (Number.isFinite(numericKey) && Number.isFinite(numericValue)) {
result[numericKey] = numericValue;
}

return result;
}, {});
};

const TopUp = () => {
const { t } = useTranslation();
const [searchParams, setSearchParams] = useSearchParams();
Expand Down Expand Up @@ -110,6 +144,7 @@ const TopUp = () => {
const [topupInfo, setTopupInfo] = useState({
amount_options: [],
discount: {},
topup_group_ratio: 1,
});

const confirmPayMethods = [
Expand Down Expand Up @@ -575,9 +610,11 @@ const TopUp = () => {
const res = await API.get('/api/user/topup/info');
const { message, data, success } = res.data;
if (success) {
const parsedDiscounts = parseDiscountMap(data.discount);
setTopupInfo({
amount_options: data.amount_options || [],
discount: data.discount || {},
discount: parsedDiscounts,
topup_group_ratio: Number(data.topup_group_ratio) || 1,
});

// 处理支付方式
Expand Down Expand Up @@ -680,8 +717,8 @@ const TopUp = () => {
// 如果有自定义充值数量选项,使用它们替换默认的预设选项
if (data.amount_options && data.amount_options.length > 0) {
const customPresets = data.amount_options.map((amount) => ({
value: amount,
discount: data.discount[amount] || 1.0,
value: Number(amount),
discount: parsedDiscounts[amount] || 1.0,
}));
setPresetAmounts(customPresets);
}
Expand Down Expand Up @@ -849,12 +886,21 @@ const TopUp = () => {

// 选择预设充值额度
const selectPresetAmount = (preset) => {
setTopUpCount(preset.value);
setSelectedPreset(preset.value);
const presetValue = Number(preset.value);
setTopUpCount(presetValue);
setSelectedPreset(presetValue);

// 计算实际支付金额,考虑折扣
const discount = preset.discount || topupInfo.discount[preset.value] || 1.0;
const discountedAmount = preset.value * priceRatio * discount;
const rawDiscount =
topupInfo.discount[presetValue] ??
topupInfo.discount[String(presetValue)] ??
preset.discount;
const discount = Number.isFinite(Number(rawDiscount))
? Number(rawDiscount)
: 1.0;
const topupGroupRatio = Number(topupInfo.topup_group_ratio) || 1;
const discountedAmount =
presetValue * Number(priceRatio) * topupGroupRatio * discount;
setAmount(discountedAmount);
};

Expand Down
Loading