| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001 |
- /*
- 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, { useEffect, useState, useContext, useRef } from 'react';
- import { useSearchParams } from 'react-router-dom';
- import {
- API,
- showError,
- showInfo,
- showSuccess,
- renderQuota,
- renderQuotaWithAmount,
- copy,
- getQuotaPerUnit,
- } from '../../helpers';
- import { Modal, Toast } from '@douyinfe/semi-ui';
- import { useTranslation } from 'react-i18next';
- import { UserContext } from '../../context/User';
- import { StatusContext } from '../../context/Status';
- import RechargeCard from './RechargeCard';
- import InvitationCard from './InvitationCard';
- import TransferModal from './modals/TransferModal';
- import PaymentConfirmModal from './modals/PaymentConfirmModal';
- import TopupHistoryModal from './modals/TopupHistoryModal';
- const TopUp = () => {
- const { t } = useTranslation();
- const [searchParams, setSearchParams] = useSearchParams();
- const [userState, userDispatch] = useContext(UserContext);
- const [statusState] = useContext(StatusContext);
- const [redemptionCode, setRedemptionCode] = useState('');
- const [amount, setAmount] = useState(0.0);
- const [minTopUp, setMinTopUp] = useState(statusState?.status?.min_topup || 1);
- const [topUpCount, setTopUpCount] = useState(
- statusState?.status?.min_topup || 1,
- );
- const [topUpLink, setTopUpLink] = useState('');
- const [enableOnlineTopUp, setEnableOnlineTopUp] = useState(
- statusState?.status?.enable_online_topup || false,
- );
- const [priceRatio, setPriceRatio] = useState(statusState?.status?.price || 1);
- const [enableStripeTopUp, setEnableStripeTopUp] = useState(
- statusState?.status?.enable_stripe_topup || false,
- );
- const [statusLoading, setStatusLoading] = useState(true);
- // Creem 相关状态
- const [creemProducts, setCreemProducts] = useState([]);
- const [enableCreemTopUp, setEnableCreemTopUp] = useState(false);
- const [creemOpen, setCreemOpen] = useState(false);
- const [selectedCreemProduct, setSelectedCreemProduct] = useState(null);
- // Waffo 相关状态
- const [enableWaffoTopUp, setEnableWaffoTopUp] = useState(false);
- const [waffoPayMethods, setWaffoPayMethods] = useState([]);
- const [waffoMinTopUp, setWaffoMinTopUp] = useState(1);
- const [enableWaffoPancakeTopUp, setEnableWaffoPancakeTopUp] = useState(false);
- const [waffoPancakeMinTopUp, setWaffoPancakeMinTopUp] = useState(1);
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [open, setOpen] = useState(false);
- const [payWay, setPayWay] = useState('');
- const [amountLoading, setAmountLoading] = useState(false);
- const [paymentLoading, setPaymentLoading] = useState(false);
- const [confirmLoading, setConfirmLoading] = useState(false);
- const [payMethods, setPayMethods] = useState([]);
- const affFetchedRef = useRef(false);
- // 邀请相关状态
- const [affLink, setAffLink] = useState('');
- const [openTransfer, setOpenTransfer] = useState(false);
- const [transferAmount, setTransferAmount] = useState(0);
- // 账单Modal状态
- const [openHistory, setOpenHistory] = useState(false);
- // 订阅相关
- const [subscriptionPlans, setSubscriptionPlans] = useState([]);
- const [subscriptionLoading, setSubscriptionLoading] = useState(true);
- const [billingPreference, setBillingPreference] =
- useState('subscription_first');
- const [activeSubscriptions, setActiveSubscriptions] = useState([]);
- const [allSubscriptions, setAllSubscriptions] = useState([]);
- // 预设充值额度选项
- const [presetAmounts, setPresetAmounts] = useState([]);
- const [selectedPreset, setSelectedPreset] = useState(null);
- // 充值配置信息
- const [topupInfo, setTopupInfo] = useState({
- amount_options: [],
- discount: {},
- });
- const confirmPayMethods = [
- ...payMethods,
- ...waffoPayMethods.map((method, index) => ({
- ...method,
- type: `waffo:${index}`,
- min_topup: waffoMinTopUp,
- color: method.color || 'rgba(var(--semi-primary-5), 1)',
- })),
- ];
- const getPayMethodConfig = (payment) =>
- confirmPayMethods.find((method) => method.type === payment);
- const getPaymentMinTopUp = (payment) => {
- const configuredMinTopUp = Number(getPayMethodConfig(payment)?.min_topup);
- return Number.isFinite(configuredMinTopUp) && configuredMinTopUp > 0
- ? configuredMinTopUp
- : minTopUp;
- };
- const requestAmountByPayment = async (payment, value) => {
- if (payment === 'stripe') {
- return getStripeAmount(value);
- }
- if (payment === 'waffo_pancake') {
- return getWaffoPancakeAmount(value);
- }
- if (typeof payment === 'string' && payment.startsWith('waffo:')) {
- return getWaffoAmount(value);
- }
- return getAmount(value);
- };
- const topUp = async () => {
- if (redemptionCode === '') {
- showInfo(t('请输入兑换码!'));
- return;
- }
- setIsSubmitting(true);
- try {
- const res = await API.post('/api/user/topup', {
- key: redemptionCode,
- });
- const { success, message, data } = res.data;
- if (success) {
- showSuccess(t('兑换成功!'));
- Modal.success({
- title: t('兑换成功!'),
- content: t('成功兑换额度:') + renderQuota(data),
- centered: true,
- });
- if (userState.user) {
- const updatedUser = {
- ...userState.user,
- quota: userState.user.quota + data,
- };
- userDispatch({ type: 'login', payload: updatedUser });
- }
- setRedemptionCode('');
- } else {
- showError(message);
- }
- } catch (err) {
- showError(t('请求失败'));
- } finally {
- setIsSubmitting(false);
- }
- };
- const openTopUpLink = () => {
- if (!topUpLink) {
- showError(t('超级管理员未设置充值链接!'));
- return;
- }
- window.open(topUpLink, '_blank');
- };
- const preTopUp = async (payment) => {
- if (payment === 'stripe') {
- if (!enableStripeTopUp) {
- showError(t('管理员未开启Stripe充值!'));
- return;
- }
- } else if (payment === 'waffo_pancake') {
- if (!enableWaffoPancakeTopUp) {
- showError(t('管理员未开启 Waffo Pancake 充值!'));
- return;
- }
- } else if (payment.startsWith('waffo:')) {
- if (!enableWaffoTopUp) {
- showError(t('管理员未开启 Waffo 充值!'));
- return;
- }
- } else {
- if (!enableOnlineTopUp) {
- showError(t('管理员未开启在线充值!'));
- return;
- }
- }
- setPayWay(payment);
- setPaymentLoading(true);
- try {
- const selectedMinTopUp = getPaymentMinTopUp(payment);
- await requestAmountByPayment(payment);
- if (topUpCount < selectedMinTopUp) {
- showError(t('充值数量不能小于') + selectedMinTopUp);
- return;
- }
- setOpen(true);
- } catch (error) {
- showError(t('获取金额失败'));
- } finally {
- setPaymentLoading(false);
- }
- };
- const onlineTopUp = async () => {
- if (payWay === 'waffo_pancake') {
- setConfirmLoading(true);
- try {
- await waffoPancakeTopUp();
- } finally {
- setOpen(false);
- setConfirmLoading(false);
- }
- return;
- }
- if (payWay.startsWith('waffo:')) {
- const payMethodIndex = Number(payWay.split(':')[1]);
- setConfirmLoading(true);
- try {
- await waffoTopUp(Number.isFinite(payMethodIndex) ? payMethodIndex : 0);
- } finally {
- setOpen(false);
- setConfirmLoading(false);
- }
- return;
- }
- if (payWay === 'stripe') {
- // Stripe 支付处理
- if (amount === 0) {
- await getStripeAmount();
- }
- } else {
- // 普通支付处理
- if (amount === 0) {
- await getAmount();
- }
- }
- if (topUpCount < minTopUp) {
- showError('充值数量不能小于' + minTopUp);
- return;
- }
- setConfirmLoading(true);
- try {
- let res;
- if (payWay === 'stripe') {
- // Stripe 支付请求
- res = await API.post('/api/user/stripe/pay', {
- amount: parseInt(topUpCount),
- payment_method: 'stripe',
- });
- } else {
- // 普通支付请求
- res = await API.post('/api/user/pay', {
- amount: parseInt(topUpCount),
- payment_method: payWay,
- });
- }
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- if (payWay === 'stripe') {
- // Stripe 支付回调处理
- window.open(data.pay_link, '_blank');
- } else {
- // 普通支付表单提交
- let params = data;
- let url = res.data.url;
- let form = document.createElement('form');
- form.action = url;
- form.method = 'POST';
- let isSafari =
- navigator.userAgent.indexOf('Safari') > -1 &&
- navigator.userAgent.indexOf('Chrome') < 1;
- if (!isSafari) {
- form.target = '_blank';
- }
- for (let key in params) {
- let 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);
- }
- } else {
- const errorMsg =
- typeof data === 'string' ? data : message || t('支付失败');
- showError(errorMsg);
- }
- } else {
- showError(res);
- }
- } catch (err) {
- showError(t('支付请求失败'));
- } finally {
- setOpen(false);
- setConfirmLoading(false);
- }
- };
- const creemPreTopUp = async (product) => {
- if (!enableCreemTopUp) {
- showError(t('管理员未开启 Creem 充值!'));
- return;
- }
- setSelectedCreemProduct(product);
- setCreemOpen(true);
- };
- const onlineCreemTopUp = async () => {
- if (!selectedCreemProduct) {
- showError(t('请选择产品'));
- return;
- }
- // Validate product has required fields
- if (!selectedCreemProduct.productId) {
- showError(t('产品配置错误,请联系管理员'));
- return;
- }
- setConfirmLoading(true);
- try {
- const res = await API.post('/api/user/creem/pay', {
- product_id: selectedCreemProduct.productId,
- payment_method: 'creem',
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- processCreemCallback(data);
- } else {
- const errorMsg =
- typeof data === 'string' ? data : message || t('支付失败');
- showError(errorMsg);
- }
- } else {
- showError(res);
- }
- } catch (err) {
- showError(t('支付请求失败'));
- } finally {
- setCreemOpen(false);
- setConfirmLoading(false);
- }
- };
- const waffoTopUp = async (payMethodIndex) => {
- try {
- if (topUpCount < waffoMinTopUp) {
- showError(t('充值数量不能小于') + waffoMinTopUp);
- return;
- }
- setPaymentLoading(true);
- const requestBody = {
- amount: parseInt(topUpCount),
- };
- if (payMethodIndex != null) {
- requestBody.pay_method_index = payMethodIndex;
- }
- const res = await API.post('/api/user/waffo/pay', requestBody);
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success' && data?.payment_url) {
- window.open(data.payment_url, '_blank');
- } else {
- showError(data || t('支付请求失败'));
- }
- } else {
- showError(res);
- }
- } catch (e) {
- showError(t('支付请求失败'));
- } finally {
- setPaymentLoading(false);
- }
- };
- const getWaffoAmount = async (value) => {
- if (value === undefined) {
- value = topUpCount;
- }
- setAmountLoading(true);
- try {
- const res = await API.post('/api/user/waffo/amount', {
- amount: parseInt(value),
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- setAmount(parseFloat(data));
- } else {
- setAmount(0);
- Toast.error({ content: '错误:' + data, id: 'getAmount' });
- }
- } else {
- showError(res);
- }
- } catch (err) {
- // amount fetch failed silently
- } finally {
- setAmountLoading(false);
- }
- };
- const waffoPancakeTopUp = async () => {
- const minTopUpValue = Number(waffoPancakeMinTopUp || 1);
- if (topUpCount < minTopUpValue) {
- showError(t('充值数量不能小于') + minTopUpValue);
- return;
- }
- setPaymentLoading(true);
- try {
- const res = await API.post('/api/user/waffo-pancake/pay', {
- amount: parseInt(topUpCount),
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- const checkoutUrl = data?.checkout_url || '';
- if (checkoutUrl) {
- window.open(checkoutUrl, '_blank');
- } else {
- showError(t('支付请求失败'));
- }
- } else {
- const errorMsg =
- typeof data === 'string' ? data : message || t('支付请求失败');
- showError(errorMsg);
- }
- } else {
- showError(res);
- }
- } catch (e) {
- showError(t('支付请求失败'));
- } finally {
- setPaymentLoading(false);
- }
- };
- const getWaffoPancakeAmount = async (value) => {
- if (value === undefined) {
- value = topUpCount;
- }
- setAmountLoading(true);
- try {
- const res = await API.post('/api/user/waffo-pancake/amount', {
- amount: parseInt(value),
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- setAmount(parseFloat(data));
- } else {
- setAmount(0);
- Toast.error({ content: '错误:' + data, id: 'getAmount' });
- }
- } else {
- showError(res);
- }
- } catch (err) {
- // amount fetch failed silently
- } finally {
- setAmountLoading(false);
- }
- };
- const processCreemCallback = (data) => {
- // 与 Stripe 保持一致的实现方式
- window.open(data.checkout_url, '_blank');
- };
- const getUserQuota = async () => {
- let res = await API.get(`/api/user/self`);
- const { success, message, data } = res.data;
- if (success) {
- userDispatch({ type: 'login', payload: data });
- } else {
- showError(message);
- }
- };
- const getSubscriptionPlans = async () => {
- setSubscriptionLoading(true);
- try {
- const res = await API.get('/api/subscription/plans');
- if (res.data?.success) {
- setSubscriptionPlans(res.data.data || []);
- }
- } catch (e) {
- setSubscriptionPlans([]);
- } finally {
- setSubscriptionLoading(false);
- }
- };
- const getSubscriptionSelf = async () => {
- try {
- const res = await API.get('/api/subscription/self');
- if (res.data?.success) {
- setBillingPreference(
- res.data.data?.billing_preference || 'subscription_first',
- );
- // Active subscriptions
- const activeSubs = res.data.data?.subscriptions || [];
- setActiveSubscriptions(activeSubs);
- // All subscriptions (including expired)
- const allSubs = res.data.data?.all_subscriptions || [];
- setAllSubscriptions(allSubs);
- }
- } catch (e) {
- // ignore
- }
- };
- const updateBillingPreference = async (pref) => {
- const previousPref = billingPreference;
- setBillingPreference(pref);
- try {
- const res = await API.put('/api/subscription/self/preference', {
- billing_preference: pref,
- });
- if (res.data?.success) {
- showSuccess(t('更新成功'));
- const normalizedPref =
- res.data?.data?.billing_preference || pref || previousPref;
- setBillingPreference(normalizedPref);
- } else {
- showError(res.data?.message || t('更新失败'));
- setBillingPreference(previousPref);
- }
- } catch (e) {
- showError(t('请求失败'));
- setBillingPreference(previousPref);
- }
- };
- // 获取充值配置信息
- const getTopupInfo = async () => {
- try {
- const res = await API.get('/api/user/topup/info');
- const { message, data, success } = res.data;
- if (success) {
- setTopupInfo({
- amount_options: data.amount_options || [],
- discount: data.discount || {},
- });
- // 处理支付方式
- let payMethods = data.pay_methods || [];
- try {
- if (typeof payMethods === 'string') {
- payMethods = JSON.parse(payMethods);
- }
- if (payMethods && payMethods.length > 0) {
- // 检查name和type是否为空
- payMethods = payMethods.filter((method) => {
- return method.name && method.type;
- });
- // 如果没有color,则设置默认颜色
- payMethods = payMethods.map((method) => {
- // 规范化最小充值数
- const normalizedMinTopup = Number(method.min_topup);
- method.min_topup = Number.isFinite(normalizedMinTopup)
- ? normalizedMinTopup
- : 0;
- // Stripe 的最小充值从后端字段回填
- if (
- method.type === 'stripe' &&
- (!method.min_topup || method.min_topup <= 0)
- ) {
- const stripeMin = Number(data.stripe_min_topup);
- if (Number.isFinite(stripeMin)) {
- method.min_topup = stripeMin;
- }
- }
- if (!method.color) {
- if (method.type === 'alipay') {
- method.color = 'rgba(var(--semi-blue-5), 1)';
- } else if (method.type === 'wxpay') {
- method.color = 'rgba(var(--semi-green-5), 1)';
- } else if (method.type === 'stripe') {
- method.color = 'rgba(var(--semi-purple-5), 1)';
- } else {
- method.color = 'rgba(var(--semi-primary-5), 1)';
- }
- }
- return method;
- });
- } else {
- payMethods = [];
- }
- // 如果启用了 Stripe 支付,添加到支付方法列表
- // 这个逻辑现在由后端处理,如果 Stripe 启用,后端会在 pay_methods 中包含它
- setPayMethods(payMethods);
- const enableStripeTopUp = data.enable_stripe_topup || false;
- const enableOnlineTopUp = data.enable_online_topup || false;
- const enableCreemTopUp = data.enable_creem_topup || false;
- const enableWaffoTopUp = data.enable_waffo_topup || false;
- const enableWaffoPancakeTopUp =
- data.enable_waffo_pancake_topup || false;
- const minTopUpValue = enableOnlineTopUp
- ? data.min_topup
- : enableStripeTopUp
- ? data.stripe_min_topup
- : enableWaffoTopUp
- ? data.waffo_min_topup
- : enableWaffoPancakeTopUp
- ? data.waffo_pancake_min_topup
- : 1;
- setEnableOnlineTopUp(enableOnlineTopUp);
- setEnableStripeTopUp(enableStripeTopUp);
- setEnableCreemTopUp(enableCreemTopUp);
- setEnableWaffoTopUp(enableWaffoTopUp);
- setWaffoPayMethods(data.waffo_pay_methods || []);
- setWaffoMinTopUp(data.waffo_min_topup || 1);
- setEnableWaffoPancakeTopUp(enableWaffoPancakeTopUp);
- setWaffoPancakeMinTopUp(data.waffo_pancake_min_topup || 1);
- setMinTopUp(minTopUpValue);
- setTopUpCount(minTopUpValue);
- setTopUpLink(data.topup_link || '');
- // 设置 Creem 产品
- try {
- const products = JSON.parse(data.creem_products || '[]');
- setCreemProducts(products);
- } catch (e) {
- setCreemProducts([]);
- }
- // 如果没有自定义充值数量选项,根据最小充值金额生成预设充值额度选项
- if (topupInfo.amount_options.length === 0) {
- setPresetAmounts(generatePresetAmounts(minTopUpValue));
- }
- // 初始化显示实付金额
- getAmount(minTopUpValue);
- } catch (e) {
- setPayMethods([]);
- }
- // 如果有自定义充值数量选项,使用它们替换默认的预设选项
- if (data.amount_options && data.amount_options.length > 0) {
- const customPresets = data.amount_options.map((amount) => ({
- value: amount,
- discount: data.discount[amount] || 1.0,
- }));
- setPresetAmounts(customPresets);
- }
- } else {
- showError(data || t('获取充值配置失败'));
- }
- } catch (error) {
- showError(t('获取充值配置异常'));
- }
- };
- // 获取邀请链接
- const getAffLink = async () => {
- const res = await API.get('/api/user/aff');
- const { success, message, data } = res.data;
- if (success) {
- let link = `${window.location.origin}/register?aff=${data}`;
- setAffLink(link);
- } else {
- showError(message);
- }
- };
- // 划转邀请额度
- const transfer = async () => {
- if (transferAmount < getQuotaPerUnit()) {
- showError(t('划转金额最低为') + ' ' + renderQuota(getQuotaPerUnit()));
- return;
- }
- const res = await API.post(`/api/user/aff_transfer`, {
- quota: transferAmount,
- });
- const { success, message } = res.data;
- if (success) {
- showSuccess(message);
- setOpenTransfer(false);
- getUserQuota().then();
- } else {
- showError(message);
- }
- };
- // 复制邀请链接
- const handleAffLinkClick = async () => {
- await copy(affLink);
- showSuccess(t('邀请链接已复制到剪切板'));
- };
- // URL 参数自动打开账单弹窗(支付回跳时触发)
- useEffect(() => {
- if (searchParams.get('show_history') === 'true') {
- setOpenHistory(true);
- searchParams.delete('show_history');
- setSearchParams(searchParams, { replace: true });
- }
- }, []);
- useEffect(() => {
- // 始终获取最新用户数据,确保余额等统计信息准确
- getUserQuota().then();
- setTransferAmount(getQuotaPerUnit());
- }, []);
- useEffect(() => {
- if (affFetchedRef.current) return;
- affFetchedRef.current = true;
- getAffLink().then();
- }, []);
- // 在 statusState 可用时获取充值信息
- useEffect(() => {
- getTopupInfo().then();
- getSubscriptionPlans().then();
- getSubscriptionSelf().then();
- }, []);
- useEffect(() => {
- if (statusState?.status) {
- // const minTopUpValue = statusState.status.min_topup || 1;
- // setMinTopUp(minTopUpValue);
- // setTopUpCount(minTopUpValue);
- setPriceRatio(statusState.status.price || 1);
- setStatusLoading(false);
- }
- }, [statusState?.status]);
- const renderAmount = () => {
- return amount + ' ' + t('元');
- };
- const getAmount = async (value) => {
- if (value === undefined) {
- value = topUpCount;
- }
- setAmountLoading(true);
- try {
- const res = await API.post('/api/user/amount', {
- amount: parseFloat(value),
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- setAmount(parseFloat(data));
- } else {
- setAmount(0);
- Toast.error({ content: '错误:' + data, id: 'getAmount' });
- }
- } else {
- showError(res);
- }
- } catch (err) {
- // amount fetch failed silently
- }
- setAmountLoading(false);
- };
- const getStripeAmount = async (value) => {
- if (value === undefined) {
- value = topUpCount;
- }
- setAmountLoading(true);
- try {
- const res = await API.post('/api/user/stripe/amount', {
- amount: parseFloat(value),
- });
- if (res !== undefined) {
- const { message, data } = res.data;
- if (message === 'success') {
- setAmount(parseFloat(data));
- } else {
- setAmount(0);
- Toast.error({ content: '错误:' + data, id: 'getAmount' });
- }
- } else {
- showError(res);
- }
- } catch (err) {
- // amount fetch failed silently
- } finally {
- setAmountLoading(false);
- }
- };
- const handleCancel = () => {
- setOpen(false);
- };
- const handleTransferCancel = () => {
- setOpenTransfer(false);
- };
- const handleOpenHistory = () => {
- setOpenHistory(true);
- };
- const handleHistoryCancel = () => {
- setOpenHistory(false);
- };
- const handleCreemCancel = () => {
- setCreemOpen(false);
- setSelectedCreemProduct(null);
- };
- // 选择预设充值额度
- const selectPresetAmount = (preset) => {
- setTopUpCount(preset.value);
- setSelectedPreset(preset.value);
- // 计算实际支付金额,考虑折扣
- const discount = preset.discount || topupInfo.discount[preset.value] || 1.0;
- const discountedAmount = preset.value * priceRatio * discount;
- setAmount(discountedAmount);
- };
- // 格式化大数字显示
- const formatLargeNumber = (num) => {
- return num.toString();
- };
- // 根据最小充值金额生成预设充值额度选项
- const generatePresetAmounts = (minAmount) => {
- const multipliers = [1, 5, 10, 30, 50, 100, 300, 500];
- return multipliers.map((multiplier) => ({
- value: minAmount * multiplier,
- }));
- };
- return (
- <div className='w-full max-w-7xl mx-auto relative min-h-screen lg:min-h-0 mt-[60px] px-2'>
- {/* 划转模态框 */}
- <TransferModal
- t={t}
- openTransfer={openTransfer}
- transfer={transfer}
- handleTransferCancel={handleTransferCancel}
- userState={userState}
- renderQuota={renderQuota}
- getQuotaPerUnit={getQuotaPerUnit}
- transferAmount={transferAmount}
- setTransferAmount={setTransferAmount}
- />
- {/* 充值确认模态框 */}
- <PaymentConfirmModal
- t={t}
- open={open}
- onlineTopUp={onlineTopUp}
- handleCancel={handleCancel}
- confirmLoading={confirmLoading}
- topUpCount={topUpCount}
- renderQuotaWithAmount={renderQuotaWithAmount}
- amountLoading={amountLoading}
- renderAmount={renderAmount}
- payWay={payWay}
- payMethods={confirmPayMethods}
- amountNumber={amount}
- discountRate={topupInfo?.discount?.[topUpCount] || 1.0}
- />
- {/* 充值账单模态框 */}
- <TopupHistoryModal
- visible={openHistory}
- onCancel={handleHistoryCancel}
- t={t}
- />
- {/* Creem 充值确认模态框 */}
- <Modal
- title={t('确定要充值 $')}
- visible={creemOpen}
- onOk={onlineCreemTopUp}
- onCancel={handleCreemCancel}
- maskClosable={false}
- size='small'
- centered
- confirmLoading={confirmLoading}
- >
- {selectedCreemProduct && (
- <>
- <p>
- {t('产品名称')}:{selectedCreemProduct.name}
- </p>
- <p>
- {t('价格')}:{selectedCreemProduct.currency === 'EUR' ? '€' : '$'}
- {selectedCreemProduct.price}
- </p>
- <p>
- {t('充值额度')}:{selectedCreemProduct.quota}
- </p>
- <p>{t('是否确认充值?')}</p>
- </>
- )}
- </Modal>
- {/* 主布局区域 */}
- <div className='grid grid-cols-1 lg:grid-cols-2 gap-6'>
- <RechargeCard
- t={t}
- enableOnlineTopUp={enableOnlineTopUp}
- enableStripeTopUp={enableStripeTopUp}
- enableCreemTopUp={enableCreemTopUp}
- creemProducts={creemProducts}
- creemPreTopUp={creemPreTopUp}
- enableWaffoTopUp={enableWaffoTopUp}
- enableWaffoPancakeTopUp={enableWaffoPancakeTopUp}
- presetAmounts={presetAmounts}
- selectedPreset={selectedPreset}
- selectPresetAmount={selectPresetAmount}
- formatLargeNumber={formatLargeNumber}
- priceRatio={priceRatio}
- topUpCount={topUpCount}
- minTopUp={minTopUp}
- renderQuotaWithAmount={renderQuotaWithAmount}
- getAmount={getAmount}
- setTopUpCount={setTopUpCount}
- setSelectedPreset={setSelectedPreset}
- renderAmount={renderAmount}
- amountLoading={amountLoading}
- payMethods={confirmPayMethods}
- preTopUp={preTopUp}
- paymentLoading={paymentLoading}
- payWay={payWay}
- redemptionCode={redemptionCode}
- setRedemptionCode={setRedemptionCode}
- topUp={topUp}
- isSubmitting={isSubmitting}
- topUpLink={topUpLink}
- openTopUpLink={openTopUpLink}
- userState={userState}
- renderQuota={renderQuota}
- statusLoading={statusLoading}
- topupInfo={topupInfo}
- onOpenHistory={handleOpenHistory}
- subscriptionLoading={subscriptionLoading}
- subscriptionPlans={subscriptionPlans}
- billingPreference={billingPreference}
- onChangeBillingPreference={updateBillingPreference}
- activeSubscriptions={activeSubscriptions}
- allSubscriptions={allSubscriptions}
- reloadSubscriptionSelf={getSubscriptionSelf}
- />
- <InvitationCard
- t={t}
- userState={userState}
- renderQuota={renderQuota}
- setOpenTransfer={setOpenTransfer}
- affLink={affLink}
- handleAffLinkClick={handleAffLinkClick}
- />
- </div>
- </div>
- );
- };
- export default TopUp;
|