Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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,
}
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 @@ -112,6 +146,7 @@ const TopUp = () => {
const [topupInfo, setTopupInfo] = useState({
amount_options: [],
discount: {},
topup_group_ratio: 1,
});

const confirmPayMethods = [
Expand Down Expand Up @@ -577,9 +612,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 @@ -681,8 +718,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 @@ -851,12 +888,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
Loading