From 8ebe62e748560b3fd760a4ca771ef8d4cb372d77 Mon Sep 17 00:00:00 2001 From: heheer Date: Fri, 3 Jan 2025 10:44:41 +0800 Subject: [PATCH] optimize payment process (#3517) --- packages/global/common/error/code/team.ts | 1 + .../service/support/permission/teamLimit.ts | 4 +- .../web/components/common/Icon/constants.ts | 1 + .../common/Icon/icons/common/wallet.svg | 4 + packages/web/i18n/en/common.json | 4 + packages/web/i18n/zh-CN/common.json | 4 + packages/web/i18n/zh-Hant/common.json | 4 + projects/app/src/components/Layout/index.tsx | 4 +- .../wallet/NotSufficientModal/index.tsx | 159 +++++++++++++++--- projects/app/src/pages/account/team/index.tsx | 6 +- .../dataset/component/ApiDatasetForm.tsx | 2 +- .../src/pages/price/components/ExtraPlan.tsx | 23 +-- .../src/pages/price/components/Standard.tsx | 24 +-- projects/app/src/pages/price/index.tsx | 54 ++++-- projects/app/src/web/common/api/fetch.ts | 2 +- projects/app/src/web/common/api/request.ts | 9 +- .../src/web/common/system/useSystemStore.ts | 18 +- 17 files changed, 237 insertions(+), 86 deletions(-) create mode 100644 packages/web/components/common/Icon/icons/common/wallet.svg diff --git a/packages/global/common/error/code/team.ts b/packages/global/common/error/code/team.ts index e1b7a80d1f64..30ab14fc549b 100644 --- a/packages/global/common/error/code/team.ts +++ b/packages/global/common/error/code/team.ts @@ -4,6 +4,7 @@ import type { ErrType } from '../errorCode'; export enum TeamErrEnum { teamOverSize = 'teamOverSize', unAuthTeam = 'unAuthTeam', + teamMemberOverSize = 'teamMemberOverSize', aiPointsNotEnough = 'aiPointsNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough', datasetAmountNotEnough = 'datasetAmountNotEnough', diff --git a/packages/service/support/permission/teamLimit.ts b/packages/service/support/permission/teamLimit.ts index 814480ccf549..1a383f85b894 100644 --- a/packages/service/support/permission/teamLimit.ts +++ b/packages/service/support/permission/teamLimit.ts @@ -19,9 +19,7 @@ export const checkDatasetLimit = async ({ if (!standardConstants) return; if (usedDatasetSize + insertLen >= datasetMaxSize) { - return Promise.reject( - `您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedDatasetSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。` - ); + return Promise.reject(TeamErrEnum.datasetSizeNotEnough); } if (usedPoints >= totalPoints) { diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 16cabf5538f3..890e5b1bd87d 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -96,6 +96,7 @@ export const iconPaths = { 'common/variable': () => import('./icons/common/variable.svg'), 'common/viewLight': () => import('./icons/common/viewLight.svg'), 'common/voiceLight': () => import('./icons/common/voiceLight.svg'), + 'common/wallet': () => import('./icons/common/wallet.svg'), 'common/warn': () => import('./icons/common/warn.svg'), 'common/wechatFill': () => import('./icons/common/wechatFill.svg'), configmap: () => import('./icons/configmap.svg'), diff --git a/packages/web/components/common/Icon/icons/common/wallet.svg b/packages/web/components/common/Icon/icons/common/wallet.svg new file mode 100644 index 000000000000..add3e92578c6 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/wallet.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index ec0b305d0c30..b4552c29d183 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -1068,10 +1068,14 @@ "support.wallet.Ai point every thousand tokens_input": "Input:{{points}} points/1K tokens", "support.wallet.Ai point every thousand tokens_output": "Output:{{points}} points/1K tokens", "support.wallet.Amount": "Amount", + "support.wallet.App_amount_not_sufficient": "The number of your applications has reached the limit. Please upgrade your plan to continue using.", "support.wallet.Buy": "Buy", + "support.wallet.Dataset_amount_not_sufficient": "The number of your datasets has reached the limit. Please upgrade your plan to continue using.", + "support.wallet.Dataset_not_sufficient": "Your dataset capacity is insufficient. Please upgrade your plan or purchase additional dataset capacity to continue using.", "support.wallet.Not sufficient": "Insufficient AI Points, Please Upgrade Your Package or Purchase Additional AI Points to Continue Using.", "support.wallet.Plan expired time": "Package Expiration Time", "support.wallet.Standard Plan Detail": "Package Details", + "support.wallet.Team_member_over_size": "The number of your team members has reached the limit. Please upgrade your plan to continue using.", "support.wallet.To read plan": "View Package", "support.wallet.amount_0": "Purchase Quantity Cannot Be 0", "support.wallet.apply_invoice": "Apply for Invoice", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index c81c5f9cb118..21bd955f2e27 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -1071,10 +1071,14 @@ "support.wallet.Ai point every thousand tokens_input": "输入:{{points}} 积分/1K tokens", "support.wallet.Ai point every thousand tokens_output": "输出:{{points}} 积分/1K tokens", "support.wallet.Amount": "金额", + "support.wallet.App_amount_not_sufficient": "您的应用数量已达上限,请升级套餐后继续使用。", "support.wallet.Buy": "购买", + "support.wallet.Dataset_amount_not_sufficient": "您的知识库数量已达上限,请升级套餐后继续使用。", + "support.wallet.Dataset_not_sufficient": "您的知识库容量不足,请先升级套餐或购买额外知识库容量后继续使用。", "support.wallet.Not sufficient": "您的 AI 积分不足,请先升级套餐或购买额外 AI 积分后继续使用。", "support.wallet.Plan expired time": "套餐到期时间", "support.wallet.Standard Plan Detail": "套餐详情", + "support.wallet.Team_member_over_size": "您的团队成员数量已达上限,请升级套餐后继续使用。", "support.wallet.To read plan": "查看套餐", "support.wallet.amount_0": "购买数量不能为0", "support.wallet.apply_invoice": "申请开票", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 67e2a03fe060..44c3943abae7 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -1068,10 +1068,14 @@ "support.wallet.Ai point every thousand tokens_input": "輸入:{{points}} 积分/1K tokens", "support.wallet.Ai point every thousand tokens_output": "輸出:{{points}} 积分/1K tokens", "support.wallet.Amount": "金額", + "support.wallet.App_amount_not_sufficient": "您的應用數量已達上限,請升級套餐後繼續使用。", "support.wallet.Buy": "購買", + "support.wallet.Dataset_amount_not_sufficient": "您的知識庫數量已達上限,請升級套餐後繼續使用。", + "support.wallet.Dataset_not_sufficient": "您的知識庫容量不足,請先升級套餐或購買額外知識庫容量後繼續使用。", "support.wallet.Not sufficient": "您的 AI 點數不足,請先升級方案或購買額外 AI 點數後繼續使用。", "support.wallet.Plan expired time": "方案到期時間", "support.wallet.Standard Plan Detail": "方案詳細資訊", + "support.wallet.Team_member_over_size": "您的團隊成員數量已達上限,請升級套餐後繼續使用。", "support.wallet.To read plan": "檢視方案", "support.wallet.amount_0": "購買數量不能為 0", "support.wallet.apply_invoice": "申請發票", diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index a8734e5c1f15..0fe546023494 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -51,7 +51,7 @@ export const navbarWidth = '64px'; const Layout = ({ children }: { children: JSX.Element }) => { const router = useRouter(); const { Loading } = useLoading(); - const { loading, feConfigs, isNotSufficientModal } = useSystemStore(); + const { loading, feConfigs, notSufficientModalType } = useSystemStore(); const { isPc } = useSystem(); const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore(); const { setUserDefaultLng } = useI18nLng(); @@ -121,7 +121,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { {feConfigs?.isPlus && ( <> {!!userInfo && } - {isNotSufficientModal && } + {notSufficientModalType && } {!!userInfo && } {showUpdateNotification && ( setIsUpdateNotification(false)} /> diff --git a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx index 94721c957757..3f413c4d3d4e 100644 --- a/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx +++ b/projects/app/src/components/support/wallet/NotSufficientModal/index.tsx @@ -1,35 +1,148 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; -import { Button, ModalBody, ModalFooter } from '@chakra-ui/react'; -import { useRouter } from 'next/router'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { Box, Button, Flex, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react'; +import { NotSufficientModalType, useSystemStore } from '@/web/common/system/useSystemStore'; +import ExtraPlan from '@/pages/price/components/ExtraPlan'; +import StandardPlan from '@/pages/price/components/Standard'; +import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; +import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; +import { useUserStore } from '@/web/support/user/useUserStore'; +import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; -const NotSufficientModal = () => { +const NotSufficientModal = ({ type }: { type: NotSufficientModalType }) => { const { t } = useTranslation(); - const router = useRouter(); - const { setIsNotSufficientModal } = useSystemStore(); + const { setNotSufficientModalType } = useSystemStore(); - const onClose = () => setIsNotSufficientModal(false); + const onClose = () => setNotSufficientModalType(undefined); + + const { + isOpen: isRechargeModalOpen, + onOpen: onRechargeModalOpen, + onClose: onRechargeModalClose + } = useDisclosure(); + + const textMap = { + [TeamErrEnum.aiPointsNotEnough]: t('common:support.wallet.Not sufficient'), + [TeamErrEnum.datasetSizeNotEnough]: t('common:support.wallet.Dataset_not_sufficient'), + [TeamErrEnum.datasetAmountNotEnough]: t('common:support.wallet.Dataset_amount_not_sufficient'), + [TeamErrEnum.teamMemberOverSize]: t('common:support.wallet.Team_member_over_size'), + [TeamErrEnum.appAmountNotEnough]: t('common:support.wallet.App_amount_not_sufficient') + }; + + return ( + <> + + {textMap[type]} + + + + + + + {isRechargeModalOpen && ( + + )} + + ); +}; + +export default NotSufficientModal; + +const RechargeModal = ({ + onClose, + onPaySuccess +}: { + onClose: () => void; + onPaySuccess: () => void; +}) => { + const { t } = useTranslation(); + const { teamPlanStatus } = useUserStore(); + + const planName = useMemo(() => { + if (!teamPlanStatus?.standard?.currentSubLevel) return ''; + return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label; + }, [teamPlanStatus?.standard?.currentSubLevel]); + + const [tab, setTab] = useState<'standard' | 'extra'>('standard'); return ( - - {t('common:support.wallet.Not sufficient')} - - - - + {tab === 'standard' ? ( + + ) : ( + + )} + + ); }; - -export default NotSufficientModal; diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx index 56af9f3cd536..0beb7c6ebc20 100644 --- a/projects/app/src/pages/account/team/index.tsx +++ b/projects/app/src/pages/account/team/index.tsx @@ -21,6 +21,7 @@ import { TeamContext, TeamModalContextProvider } from './components/context'; import dynamic from 'next/dynamic'; import TeamTagModal from '@/components/support/user/team/TeamTagModal'; import MemberTable from './components/MemberTable'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; const InviteModal = dynamic(() => import('./components/InviteModal')); const PermissionManage = dynamic(() => import('./components/PermissionManage/index')); @@ -41,7 +42,7 @@ const Team = () => { const { toast } = useToast(); const { t } = useTranslation(); const { userInfo, teamPlanStatus } = useUserStore(); - const { feConfigs } = useSystemStore(); + const { feConfigs, setNotSufficientModalType } = useSystemStore(); const { myTeams, @@ -221,10 +222,11 @@ const Team = () => { ) { toast({ status: 'warning', - title: t('user.team.Over Max Member Tip', { + title: t('common:user.team.Over Max Member Tip', { max: teamPlanStatus.standardConstants.maxTeamMember }) }); + setNotSufficientModalType(TeamErrEnum.teamMemberOverSize); } else { onOpenInvite(); } diff --git a/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx b/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx index 51c3f73be0b0..37a9f62b6ee3 100644 --- a/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx +++ b/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx @@ -135,7 +135,7 @@ const ApiDatasetForm = ({ diff --git a/projects/app/src/pages/price/components/ExtraPlan.tsx b/projects/app/src/pages/price/components/ExtraPlan.tsx index a83d6a59bf00..84cd13b40998 100644 --- a/projects/app/src/pages/price/components/ExtraPlan.tsx +++ b/projects/app/src/pages/price/components/ExtraPlan.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Grid, Button } from '@chakra-ui/react'; +import { Box, Flex, Grid, Button, VStack } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import React, { useCallback, useState } from 'react'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -11,7 +11,7 @@ import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants'; import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal'; import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput'; -const ExtraPlan = () => { +const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => { const { t } = useTranslation(); const { toast } = useToast(); const { subPlans } = useSystemStore(); @@ -108,19 +108,8 @@ const ExtraPlan = () => { ); return ( - - - {t('common:support.wallet.subscription.Extra plan')} - - - {t('common:support.wallet.subscription.Extra plan tip')} - - + + { - {!!qrPayData && } - + {!!qrPayData && } + ); }; diff --git a/projects/app/src/pages/price/components/Standard.tsx b/projects/app/src/pages/price/components/Standard.tsx index 6180d9eb5109..ea3d0dc99b37 100644 --- a/projects/app/src/pages/price/components/Standard.tsx +++ b/projects/app/src/pages/price/components/Standard.tsx @@ -20,10 +20,10 @@ export enum PackageChangeStatusEnum { const Standard = ({ standardPlan: myStandardPlan, - refetchTeamSubPlan + onPaySuccess }: { standardPlan?: TeamSubSchema; - refetchTeamSubPlan: () => void; + onPaySuccess?: () => void; }) => { const { t } = useTranslation(); @@ -78,14 +78,6 @@ const Standard = ({ return ( <> - - {t('common:support.wallet.subscription.Sub plan')} - - - {t('common:support.wallet.subscription.Sub plan tip', { - title: feConfigs?.systemTitle - })} - {!!qrPayData && packageChange && ( - + )} - - - - {t('user:bill.standard_valid_tip')} - - ); }; diff --git a/projects/app/src/pages/price/index.tsx b/projects/app/src/pages/price/index.tsx index 581acfbb6f2a..c15bd4ea0b83 100644 --- a/projects/app/src/pages/price/index.tsx +++ b/projects/app/src/pages/price/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; -import { Box, Flex } from '@chakra-ui/react'; +import { Box, Flex, HStack, VStack } from '@chakra-ui/react'; import { useUserStore } from '@/web/support/user/useUserStore'; import { getTeamPlanStatus } from '@/web/support/user/team/api'; import { useQuery } from '@tanstack/react-query'; @@ -12,17 +12,18 @@ import FAQ from './components/FAQ'; import { getToken } from '@/web/support/user/auth'; import Script from 'next/script'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; +import { useTranslation } from 'next-i18next'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; const PriceBox = () => { const { userInfo } = useUserStore(); + const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); - const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery( - ['getTeamPlanStatus'], - getTeamPlanStatus, - { - enabled: !!getToken() || !!userInfo - } - ); + const { data: teamSubPlan } = useQuery(['getTeamPlanStatus'], getTeamPlanStatus, { + enabled: !!getToken() || !!userInfo + }); return ( <> @@ -39,12 +40,39 @@ const PriceBox = () => { backgroundRepeat={'no-repeat'} > {/* standard sub */} - + + + {t('common:support.wallet.subscription.Sub plan')} + + + {t('common:support.wallet.subscription.Sub plan tip', { + title: feConfigs?.systemTitle + })} + + + + + + {t('user:bill.standard_valid_tip')} + + + - + {/* extra plan */} + + + {t('common:support.wallet.subscription.Extra plan')} + + + {t('common:support.wallet.subscription.Extra plan tip')} + + + {/* points */} diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts index d3ed1b93b3f0..ba82ed797532 100644 --- a/projects/app/src/web/common/api/fetch.ts +++ b/projects/app/src/web/common/api/fetch.ts @@ -220,7 +220,7 @@ export const streamFetch = ({ }); } else if (event === SseResponseEventEnum.error) { if (parseJson.statusText === TeamErrEnum.aiPointsNotEnough) { - useSystemStore.getState().setIsNotSufficientModal(true); + useSystemStore.getState().setNotSufficientModalType(TeamErrEnum.aiPointsNotEnough); } errMsg = getErrText(parseJson, '流响应错误'); } diff --git a/projects/app/src/web/common/api/request.ts b/projects/app/src/web/common/api/request.ts index b5a7d61e5e93..9ab8aa4d4ec5 100644 --- a/projects/app/src/web/common/api/request.ts +++ b/projects/app/src/web/common/api/request.ts @@ -120,8 +120,13 @@ function responseError(err: any) { return Promise.reject({ message: '无权操作' }); } - if (err?.statusText === TeamErrEnum.aiPointsNotEnough) { - useSystemStore.getState().setIsNotSufficientModal(true); + if ( + err?.statusText === TeamErrEnum.aiPointsNotEnough || + err?.statusText === TeamErrEnum.datasetSizeNotEnough || + err?.statusText === TeamErrEnum.datasetAmountNotEnough || + err?.statusText === TeamErrEnum.appAmountNotEnough + ) { + useSystemStore.getState().setNotSufficientModalType(err.statusText); return Promise.reject(err); } if (err?.response?.data) { diff --git a/projects/app/src/web/common/system/useSystemStore.ts b/projects/app/src/web/common/system/useSystemStore.ts index e718559453c5..da6bbe367594 100644 --- a/projects/app/src/web/common/system/useSystemStore.ts +++ b/projects/app/src/web/common/system/useSystemStore.ts @@ -14,9 +14,17 @@ import { InitDateResponse } from '@/global/common/api/systemRes'; import { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types'; import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type'; import { defaultWhisperModel } from '@fastgpt/global/core/ai/model'; +import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; type LoginStoreType = { provider: `${OAuthEnum}`; lastRoute: string; state: string }; +export type NotSufficientModalType = + | TeamErrEnum.datasetSizeNotEnough + | TeamErrEnum.aiPointsNotEnough + | TeamErrEnum.datasetAmountNotEnough + | TeamErrEnum.teamMemberOverSize + | TeamErrEnum.appAmountNotEnough; + type State = { initd: boolean; setInitd: () => void; @@ -34,8 +42,8 @@ type State = { gitStar: number; loadGitStar: () => Promise; - isNotSufficientModal: boolean; - setIsNotSufficientModal: (val: boolean) => void; + notSufficientModalType: NotSufficientModalType | undefined; + setNotSufficientModalType: (val: NotSufficientModalType | undefined) => void; initDataBufferId?: string; feConfigs: FastGPTFeConfigsType; @@ -106,10 +114,10 @@ export const useSystemStore = create()( } catch (error) {} }, - isNotSufficientModal: false, - setIsNotSufficientModal(val: boolean) { + notSufficientModalType: undefined, + setNotSufficientModalType(type: NotSufficientModalType | undefined) { set((state) => { - state.isNotSufficientModal = val; + state.notSufficientModalType = type; }); },