* New pay (#2484)

* remove sub status

* feat: new pay mode

* fix: ts

* limit
This commit is contained in:
Archer
2024-08-26 09:52:09 +08:00
committed by GitHub
parent a4c19fbd0a
commit c1d08c0ccc
17 changed files with 170 additions and 440 deletions

View File

@@ -1,16 +1,25 @@
import { StandardSubLevelEnum, SubModeEnum } from '../sub/constants';
import { BillTypeEnum } from './constants'; import { BillTypeEnum } from './constants';
export type CreateBillProps = { export type CreateStandPlanBill = {
type: BillTypeEnum; type: BillTypeEnum.standSubPlan;
level: `${StandardSubLevelEnum}`;
// balance subMode: `${SubModeEnum}`;
balance?: number; // read
month?: number;
// extra dataset size
extraDatasetSize?: number; // 1k
extraPoints?: number; // 100w
}; };
type CreateExtractPointsBill = {
type: BillTypeEnum.extraPoints;
extraPoints: number;
};
type CreateExtractDatasetBill = {
type: BillTypeEnum.extraDatasetSub;
extraDatasetSize: number;
month: number;
};
export type CreateBillProps =
| CreateStandPlanBill
| CreateExtractPointsBill
| CreateExtractDatasetBill;
export type CreateBillResponse = { export type CreateBillResponse = {
billId: string; billId: string;
codeUrl: string; codeUrl: string;

View File

@@ -19,7 +19,6 @@ export type BillSchemaType = {
month?: number; month?: number;
datasetSize?: number; datasetSize?: number;
extraPoints?: number; extraPoints?: number;
invoice: boolean;
}; };
}; };

View File

@@ -1,3 +1,5 @@
import { i18nT } from '../../../../web/i18n/utils';
export enum SubTypeEnum { export enum SubTypeEnum {
standard = 'standard', standard = 'standard',
extraDatasetSize = 'extraDatasetSize', extraDatasetSize = 'extraDatasetSize',
@@ -19,19 +21,6 @@ export const subTypeMap = {
} }
}; };
export enum SubStatusEnum {
active = 'active',
expired = 'expired'
}
export const subStatusMap = {
[SubStatusEnum.active]: {
label: 'support.wallet.subscription.status.active'
},
[SubStatusEnum.expired]: {
label: 'support.wallet.subscription.status.canceled'
}
};
export enum SubModeEnum { export enum SubModeEnum {
month = 'month', month = 'month',
year = 'year' year = 'year'
@@ -56,23 +45,28 @@ export enum StandardSubLevelEnum {
} }
export const standardSubLevelMap = { export const standardSubLevelMap = {
[StandardSubLevelEnum.free]: { [StandardSubLevelEnum.free]: {
label: 'support.wallet.subscription.standardSubLevel.free', label: i18nT('common:support.wallet.subscription.standardSubLevel.free'),
desc: 'support.wallet.subscription.standardSubLevel.free desc' desc: i18nT('common:support.wallet.subscription.standardSubLevel.free desc'),
weight: 1
}, },
[StandardSubLevelEnum.experience]: { [StandardSubLevelEnum.experience]: {
label: 'support.wallet.subscription.standardSubLevel.experience', label: i18nT('common:support.wallet.subscription.standardSubLevel.experience'),
desc: '' desc: '',
weight: 2
}, },
[StandardSubLevelEnum.team]: { [StandardSubLevelEnum.team]: {
label: 'support.wallet.subscription.standardSubLevel.team', label: i18nT('common:support.wallet.subscription.standardSubLevel.team'),
desc: '' desc: '',
weight: 3
}, },
[StandardSubLevelEnum.enterprise]: { [StandardSubLevelEnum.enterprise]: {
label: 'support.wallet.subscription.standardSubLevel.enterprise', label: i18nT('common:support.wallet.subscription.standardSubLevel.enterprise'),
desc: '' desc: '',
weight: 4
}, },
[StandardSubLevelEnum.custom]: { [StandardSubLevelEnum.custom]: {
label: 'support.wallet.subscription.standardSubLevel.custom', label: i18nT('common:support.wallet.subscription.standardSubLevel.custom'),
desc: '' desc: '',
weight: 5
} }
}; };

View File

@@ -1,4 +1,4 @@
import { StandardSubLevelEnum, SubModeEnum, SubStatusEnum, SubTypeEnum } from './constants'; import { StandardSubLevelEnum, SubModeEnum, SubTypeEnum } from './constants';
// Content of plan // Content of plan
export type TeamStandardSubPlanItemType = { export type TeamStandardSubPlanItemType = {
@@ -36,17 +36,14 @@ export type TeamSubSchema = {
_id: string; _id: string;
teamId: string; teamId: string;
type: `${SubTypeEnum}`; type: `${SubTypeEnum}`;
status: `${SubStatusEnum}`;
startTime: Date; startTime: Date;
expiredTime: Date; expiredTime: Date;
price: number;
currentMode: `${SubModeEnum}`; currentMode: `${SubModeEnum}`;
nextMode: `${SubModeEnum}`; nextMode: `${SubModeEnum}`;
currentSubLevel: `${StandardSubLevelEnum}`; currentSubLevel: StandardSubLevelEnum;
nextSubLevel: `${StandardSubLevelEnum}`; nextSubLevel: StandardSubLevelEnum;
pointPrice: number;
totalPoints: number; totalPoints: number;
surplusPoints: number; surplusPoints: number;

View File

@@ -7,10 +7,9 @@ import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo; const { Schema } = connectionMongo;
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { import {
standardSubLevelMap, StandardSubLevelEnum,
subModeMap, SubModeEnum,
subStatusMap, SubTypeEnum
subTypeMap
} from '@fastgpt/global/support/wallet/sub/constants'; } from '@fastgpt/global/support/wallet/sub/constants';
import type { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; import type { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
@@ -24,12 +23,7 @@ const SubSchema = new Schema({
}, },
type: { type: {
type: String, type: String,
enum: Object.keys(subTypeMap), enum: Object.values(SubTypeEnum),
required: true
},
status: {
type: String,
enum: Object.keys(subStatusMap),
required: true required: true
}, },
startTime: { startTime: {
@@ -40,38 +34,29 @@ const SubSchema = new Schema({
type: Date, type: Date,
required: true required: true
}, },
price: {
// last sub pay price(total price)
type: Number,
required: true
},
// standard sub // standard sub
currentMode: { currentMode: {
type: String, type: String,
enum: Object.keys(subModeMap) enum: Object.values(SubModeEnum)
}, },
nextMode: { nextMode: {
type: String, type: String,
enum: Object.keys(subModeMap) enum: Object.values(SubModeEnum)
}, },
currentSubLevel: { currentSubLevel: {
type: String, type: String,
enum: Object.keys(standardSubLevelMap) enum: Object.values(StandardSubLevelEnum)
}, },
nextSubLevel: { nextSubLevel: {
type: String, type: String,
enum: Object.keys(standardSubLevelMap) enum: Object.values(StandardSubLevelEnum)
}, },
// stand sub and extra points sub. Plan total points // stand sub and extra points sub. Plan total points
totalPoints: { totalPoints: {
type: Number type: Number
}, },
pointPrice: {
// stand level point total price
type: Number
},
surplusPoints: { surplusPoints: {
// plan surplus points // plan surplus points
type: Number type: Number

View File

@@ -1,26 +1,45 @@
import { import {
StandardSubLevelEnum, StandardSubLevelEnum,
SubModeEnum, SubModeEnum,
SubStatusEnum, SubTypeEnum,
SubTypeEnum standardSubLevelMap
} from '@fastgpt/global/support/wallet/sub/constants'; } from '@fastgpt/global/support/wallet/sub/constants';
import { MongoTeamSub } from './schema'; import { MongoTeamSub } from './schema';
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type.d'; import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type.d';
import { getVectorCountByTeamId } from '../../../common/vectorStore/controller'; import { getVectorCountByTeamId } from '../../../common/vectorStore/controller';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ClientSession } from '../../../common/mongo'; import { ClientSession } from '../../../common/mongo';
import { addMonths } from 'date-fns'; import { addMonths } from 'date-fns';
import { readFromSecondary } from '../../../common/mongo/utils';
export const getStandardPlans = () => { export const getStandardPlansConfig = () => {
return global?.subPlans?.standard; return global?.subPlans?.standard;
}; };
export const getStandardPlan = (level: `${StandardSubLevelEnum}`) => { export const getStandardPlanConfig = (level: `${StandardSubLevelEnum}`) => {
return global.subPlans?.standard?.[level]; return global.subPlans?.standard?.[level];
}; };
export const sortStandPlans = (plans: TeamSubSchema[]) => {
return plans.sort(
(a, b) =>
standardSubLevelMap[b.currentSubLevel].weight - standardSubLevelMap[a.currentSubLevel].weight
);
};
export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => { export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => {
const plans = await MongoTeamSub.find(
{
teamId,
type: SubTypeEnum.standard
},
undefined,
{
...readFromSecondary
}
);
sortStandPlans(plans);
const standardPlans = global.subPlans?.standard; const standardPlans = global.subPlans?.standard;
const standard = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard }).lean(); const standard = plans[0];
return { return {
[SubTypeEnum.standard]: standard, [SubTypeEnum.standard]: standard,
@@ -38,12 +57,11 @@ export const initTeamStandardPlan2Free = async ({
teamId: string; teamId: string;
session?: ClientSession; session?: ClientSession;
}) => { }) => {
const freePoints = global?.subPlans?.standard?.free?.totalPoints || 100; const freePoints = global?.subPlans?.standard?.[StandardSubLevelEnum.free]?.totalPoints || 100;
const teamStandardSub = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard }); const teamStandardSub = await MongoTeamSub.findOne({ teamId, type: SubTypeEnum.standard });
if (teamStandardSub) { if (teamStandardSub) {
teamStandardSub.status = SubStatusEnum.active;
teamStandardSub.currentMode = SubModeEnum.month; teamStandardSub.currentMode = SubModeEnum.month;
teamStandardSub.nextMode = SubModeEnum.month; teamStandardSub.nextMode = SubModeEnum.month;
teamStandardSub.startTime = new Date(); teamStandardSub.startTime = new Date();
@@ -52,9 +70,6 @@ export const initTeamStandardPlan2Free = async ({
teamStandardSub.currentSubLevel = StandardSubLevelEnum.free; teamStandardSub.currentSubLevel = StandardSubLevelEnum.free;
teamStandardSub.nextSubLevel = StandardSubLevelEnum.free; teamStandardSub.nextSubLevel = StandardSubLevelEnum.free;
teamStandardSub.price = 0;
teamStandardSub.pointPrice = 0;
teamStandardSub.totalPoints = freePoints; teamStandardSub.totalPoints = freePoints;
teamStandardSub.surplusPoints = teamStandardSub.surplusPoints =
teamStandardSub.surplusPoints && teamStandardSub.surplusPoints < 0 teamStandardSub.surplusPoints && teamStandardSub.surplusPoints < 0
@@ -68,13 +83,10 @@ export const initTeamStandardPlan2Free = async ({
{ {
teamId, teamId,
type: SubTypeEnum.standard, type: SubTypeEnum.standard,
status: SubStatusEnum.active,
currentMode: SubModeEnum.month, currentMode: SubModeEnum.month,
nextMode: SubModeEnum.month, nextMode: SubModeEnum.month,
startTime: new Date(), startTime: new Date(),
expiredTime: addMonths(new Date(), 1), expiredTime: addMonths(new Date(), 1),
price: 0,
pointPrice: 0,
currentSubLevel: StandardSubLevelEnum.free, currentSubLevel: StandardSubLevelEnum.free,
nextSubLevel: StandardSubLevelEnum.free, nextSubLevel: StandardSubLevelEnum.free,
@@ -94,21 +106,27 @@ export const getTeamPlanStatus = async ({
}): Promise<FeTeamPlanStatusType> => { }): Promise<FeTeamPlanStatusType> => {
const standardPlans = global.subPlans?.standard; const standardPlans = global.subPlans?.standard;
/* Get all plans and datasetSize */
const [plans, usedDatasetSize] = await Promise.all([ const [plans, usedDatasetSize] = await Promise.all([
MongoTeamSub.find({ teamId }).lean(), MongoTeamSub.find({ teamId }).lean(),
getVectorCountByTeamId(teamId) getVectorCountByTeamId(teamId)
]); ]);
const standard = plans.find((plan) => plan.type === SubTypeEnum.standard); /* Get all standardPlans and active standardPlan */
const teamStandardPlans = sortStandPlans(
plans.filter((plan) => plan.type === SubTypeEnum.standard)
);
const standardPlan = teamStandardPlans[0];
const extraDatasetSize = plans.filter((plan) => plan.type === SubTypeEnum.extraDatasetSize); const extraDatasetSize = plans.filter((plan) => plan.type === SubTypeEnum.extraDatasetSize);
const extraPoints = plans.filter((plan) => plan.type === SubTypeEnum.extraPoints); const extraPoints = plans.filter((plan) => plan.type === SubTypeEnum.extraPoints);
// Free user, first login after expiration. The free subscription plan will be reset // Free user, first login after expiration. The free subscription plan will be reset
if ( if (
standard && standardPlan &&
standard.expiredTime && standardPlan.expiredTime &&
standard.currentSubLevel === StandardSubLevelEnum.free && standardPlan.currentSubLevel === StandardSubLevelEnum.free &&
dayjs(standard.expiredTime).isBefore(new Date()) dayjs(standardPlan.expiredTime).isBefore(new Date())
) { ) {
console.log('Init free stand plan', { teamId }); console.log('Init free stand plan', { teamId });
await initTeamStandardPlan2Free({ teamId }); await initTeamStandardPlan2Free({ teamId });
@@ -116,26 +134,26 @@ export const getTeamPlanStatus = async ({
} }
const totalPoints = standardPlans const totalPoints = standardPlans
? (standard?.totalPoints || 0) + ? (standardPlan?.totalPoints || 0) +
extraPoints.reduce((acc, cur) => acc + (cur.totalPoints || 0), 0) extraPoints.reduce((acc, cur) => acc + (cur.totalPoints || 0), 0)
: Infinity; : Infinity;
const surplusPoints = const surplusPoints =
(standard?.surplusPoints || 0) + (standardPlan?.surplusPoints || 0) +
extraPoints.reduce((acc, cur) => acc + (cur.surplusPoints || 0), 0); extraPoints.reduce((acc, cur) => acc + (cur.surplusPoints || 0), 0);
const standardMaxDatasetSize = const standardMaxDatasetSize =
standard?.currentSubLevel && standardPlans standardPlan?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel]?.maxDatasetSize || Infinity ? standardPlans[standardPlan.currentSubLevel]?.maxDatasetSize || Infinity
: Infinity; : Infinity;
const totalDatasetSize = const totalDatasetSize =
standardMaxDatasetSize + standardMaxDatasetSize +
extraDatasetSize.reduce((acc, cur) => acc + (cur.currentExtraDatasetSize || 0), 0); extraDatasetSize.reduce((acc, cur) => acc + (cur.currentExtraDatasetSize || 0), 0);
return { return {
[SubTypeEnum.standard]: standard, [SubTypeEnum.standard]: standardPlan,
standardConstants: standardConstants:
standard?.currentSubLevel && standardPlans standardPlan?.currentSubLevel && standardPlans
? standardPlans[standard.currentSubLevel] ? standardPlans[standardPlan.currentSubLevel]
: undefined, : undefined,
totalPoints, totalPoints,

View File

@@ -1331,7 +1331,7 @@
"FAQ": "Frequently asked questions", "FAQ": "Frequently asked questions",
"Month amount": "Months", "Month amount": "Months",
"Next plan": "Future plan", "Next plan": "Future plan",
"Nonsupport": "Unable to switch", "Nonsupport": "No purchase required",
"Stand plan level": "Subscription plan", "Stand plan level": "Subscription plan",
"Standard update fail": "Failed to modify subscription plan", "Standard update fail": "Failed to modify subscription plan",
"Standard update success": "Subscription plan change successful!", "Standard update success": "Subscription plan change successful!",
@@ -1360,6 +1360,7 @@
"point": "integral", "point": "integral",
"rerank": "Rerank", "rerank": "Rerank",
"standardSubLevel": { "standardSubLevel": {
"custom": "Customized version",
"enterprise": "Enterprise edition", "enterprise": "Enterprise edition",
"experience": "Experience edition", "experience": "Experience edition",
"free": "Free edition", "free": "Free edition",

View File

@@ -1,6 +1,8 @@
{ {
"bill": { "bill": {
"not_need_invoice": "Balance payment, unable to issue invoice" "buy_standard_plan_success": "Package purchased successfully",
"not_need_invoice": "Balance payment, unable to issue invoice",
"renew_plan": "Renewal package"
}, },
"bind_inform_account_error": "Abnormal binding notification account", "bind_inform_account_error": "Abnormal binding notification account",
"bind_inform_account_success": "Binding notification account successful", "bind_inform_account_success": "Binding notification account successful",

View File

@@ -1116,7 +1116,7 @@
"old_package_price": "旧套餐余额", "old_package_price": "旧套餐余额",
"other": "其他金额,请取整数", "other": "其他金额,请取整数",
"to_recharge": "余额不足,去充值", "to_recharge": "余额不足,去充值",
"wechat": "请微信扫码支付: {{price}}元请勿关闭页面", "wechat": "请微信扫码支付: {{price}}元\n请勿关闭页面",
"yuan": "{{amount}}元" "yuan": "{{amount}}元"
}, },
"permission": { "permission": {
@@ -1331,7 +1331,7 @@
"FAQ": "常见问题", "FAQ": "常见问题",
"Month amount": "月数", "Month amount": "月数",
"Next plan": "未来套餐", "Next plan": "未来套餐",
"Nonsupport": "无法切换", "Nonsupport": "无需购买",
"Stand plan level": "订阅套餐", "Stand plan level": "订阅套餐",
"Standard update fail": "修改订阅套餐异常", "Standard update fail": "修改订阅套餐异常",
"Standard update success": "变更订阅套餐成功!", "Standard update success": "变更订阅套餐成功!",
@@ -1360,6 +1360,7 @@
"point": "积分", "point": "积分",
"rerank": "检索结果重排", "rerank": "检索结果重排",
"standardSubLevel": { "standardSubLevel": {
"custom": "自定义版",
"enterprise": "企业版", "enterprise": "企业版",
"experience": "体验版", "experience": "体验版",
"free": "免费版", "free": "免费版",

View File

@@ -11,7 +11,9 @@
"use_balance_hint": "由于系统升级,原“自动续费从余额扣款”模式取消,余额充值入口关闭。您的余额可用于购买积分", "use_balance_hint": "由于系统升级,原“自动续费从余额扣款”模式取消,余额充值入口关闭。您的余额可用于购买积分",
"contact_customer_service": "联系客服", "contact_customer_service": "联系客服",
"convert_success": "兑换成功", "convert_success": "兑换成功",
"convert_error": "兑换失败" "convert_error": "兑换失败",
"buy_standard_plan_success": "购买套餐成功",
"renew_plan": "续费套餐"
}, },
"bind_inform_account_error": "绑定通知账号异常", "bind_inform_account_error": "绑定通知账号异常",
"bind_inform_account_success": "绑定通知账号成功", "bind_inform_account_success": "绑定通知账号成功",

View File

@@ -1,7 +1,7 @@
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { Box, ModalBody, ModalFooter } from '@chakra-ui/react'; import { Box, ModalBody } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { checkBalancePayResult } from '@/web/support/wallet/bill/api'; import { checkBalancePayResult } from '@/web/support/wallet/bill/api';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -15,11 +15,12 @@ export type QRPayProps = {
}; };
const QRCodePayModal = ({ const QRCodePayModal = ({
tip,
readPrice, readPrice,
codeUrl, codeUrl,
billId, billId,
onSuccess onSuccess
}: QRPayProps & { onSuccess?: () => any }) => { }: QRPayProps & { tip?: string; onSuccess?: () => any }) => {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
@@ -72,11 +73,13 @@ const QRCodePayModal = ({
return ( return (
<MyModal isOpen title={t('common:user.Pay')} iconSrc="/imgs/modal/pay.svg"> <MyModal isOpen title={t('common:user.Pay')} iconSrc="/imgs/modal/pay.svg">
<ModalBody textAlign={'center'}> <ModalBody textAlign={'center'} py={6} whiteSpace={'pre'}>
<Box mb={3}>{t('common:pay.wechat', { price: readPrice })}</Box> {tip && <Box mb={3}>{tip}</Box>}
<Box id={'payQRCode'} display={'inline-block'} h={'128px'}></Box> <Box id={'payQRCode'} display={'inline-block'} h={'128px'}></Box>
<Box mt={3} textAlign={'center'}>
{t('common:pay.wechat', { price: readPrice })}
</Box>
</ModalBody> </ModalBody>
<ModalFooter />
</MyModal> </MyModal>
); );
}; };

View File

@@ -1,116 +0,0 @@
import React, { useState, useCallback, useMemo } from 'react';
import { ModalFooter, ModalBody, Button, Input, Box, Grid, Link } from '@chakra-ui/react';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useRouter } from 'next/router';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { EXTRA_PLAN_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
const PayModal = ({
onClose,
defaultValue,
onSuccess
}: {
defaultValue?: number;
onClose: () => void;
onSuccess?: () => any;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const { subPlans } = useSystemStore();
const [inputVal, setInputVal] = useState<number | undefined>(defaultValue);
const [loading, setLoading] = useState(false);
const [qrPayData, setQRPayData] = useState<QRPayProps>();
const handleClickPay = useCallback(async () => {
if (!inputVal || inputVal <= 0 || isNaN(+inputVal)) return;
setLoading(true);
try {
// 获取支付二维码
const res = await getWxPayQRCode({
type: BillTypeEnum.balance,
balance: inputVal
});
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
} catch (err) {
toast({
title: getErrText(err),
status: 'error'
});
}
setLoading(false);
}, [inputVal, toast]);
const payList = useMemo(() => {
const list = Object.values(subPlans?.standard || {});
const priceList = list.map((item) => item.price);
return priceList.concat(priceList.map((item) => item * 10)).filter(Boolean);
}, [subPlans?.standard]);
return (
<MyModal
isOpen={true}
onClose={onClose}
title={t('common:user.Pay')}
iconSrc="/imgs/modal/pay.svg"
>
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
<Box px={6} fontSize={'sm'} mb={2} py={2} maxW={'400px'}>
<Link href={EXTRA_PLAN_CARD_ROUTE} color={'primary.600'} textDecoration={'underline'}>
</Link>
</Box>
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
{payList.map((item) => (
<Button
key={item}
variant={item === inputVal ? 'solid' : 'outline'}
onClick={() => setInputVal(item)}
>
{t('common:pay.yuan', { amount: item })}
</Button>
))}
</Grid>
<Box px={6}>
<Input
value={inputVal}
type={'number'}
step={1}
placeholder={t('common:pay.other')}
onChange={(e) => {
setInputVal(Math.floor(+e.target.value));
}}
></Input>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
ml={3}
isLoading={loading}
isDisabled={!inputVal || inputVal === 0}
onClick={handleClickPay}
>
{t('common:pay.get_pay_QR')}
</Button>
</ModalFooter>
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onSuccess} />}
</MyModal>
);
};
export default PayModal;

View File

@@ -1,28 +1,17 @@
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
import { getTeamPlanStatus } from '@fastgpt/service/support/wallet/sub/utils'; import { getTeamPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
import { NextAPI } from '@/service/middleware/entry';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { const { teamId } = await authCert({
await connectToDatabase(); req,
authToken: true
});
const { teamId } = await authCert({ return getTeamPlanStatus({
req, teamId
authToken: true });
});
jsonRes<FeTeamPlanStatusType>(res, {
data: await getTeamPlanStatus({
teamId
})
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
} }
export default NextAPI(handler);

View File

@@ -123,6 +123,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
}, },
{ {
manual: false, manual: false,
throttleWait: 100,
refreshDeps: [basicNodeTemplates, nodeList, hasToolNode, templateType] refreshDeps: [basicNodeTemplates, nodeList, hasToolNode, templateType]
} }
); );

View File

@@ -1,33 +1,21 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Button, Flex, Grid, ModalBody, ModalFooter } from '@chakra-ui/react'; import { Box, Button, Flex, Grid } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants'; import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
import { postCheckStandardSub, postUpdateStandardSub } from '@/web/support/wallet/sub/api';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants'; import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
import { StandardSubPlanParams } from '@fastgpt/global/support/wallet/sub/api'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/sub/api.d';
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
import MyModal from '@fastgpt/web/components/common/MyModal';
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal'; import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
import { getWxPayQRCode } from '@/web/support/wallet/bill/api'; import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants'; import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList'; import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useToast } from '@fastgpt/web/hooks/useToast';
type ConfirmPayModalProps = {
teamBalance: number;
totalPrice: number;
payPrice: number;
planProps: StandardSubPlanParams;
};
const Standard = ({ const Standard = ({
standardPlan, standardPlan: myStandardPlan,
refetchTeamSubPlan refetchTeamSubPlan
}: { }: {
standardPlan?: TeamSubSchema; standardPlan?: TeamSubSchema;
@@ -35,8 +23,9 @@ const Standard = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { toast } = useToast();
const { subPlans, feConfigs } = useSystemStore(); const { subPlans, feConfigs } = useSystemStore();
const [confirmPayData, setConfirmPayData] = useState<ConfirmPayModalProps>();
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month); const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
const standardSubList = useMemo(() => { const standardSubList = useMemo(() => {
@@ -62,37 +51,17 @@ const Standard = ({
: []; : [];
}, [subPlans?.standard, selectSubMode]); }, [subPlans?.standard, selectSubMode]);
const { runAsync: onclickUpdateStandardPlan, loading: isUpdatingStandardPlan } = useRequest2( // Pay code
postUpdateStandardSub, const [qrPayData, setQRPayData] = useState<QRPayProps>();
{
onSuccess() {
refetchTeamSubPlan();
router.reload();
},
successToast: t('common:support.wallet.subscription.Standard update success'),
errorToast: t('common:support.wallet.subscription.Standard update fail')
}
);
const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({ /* Get pay code */
mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data), const { runAsync: onPay, loading: isLoading } = useRequest2(getWxPayQRCode, {
onSuccess(res: StandardSubPlanUpdateResponse) { onSuccess(res) {
if (res.payPrice === undefined) { setQRPayData({
onclickUpdateStandardPlan({ readPrice: res.readPrice,
level: res.nextSubLevel, codeUrl: res.codeUrl,
mode: res.nextMode billId: res.billId
}); });
} else {
setConfirmPayData({
teamBalance: res.teamBalance,
totalPrice: res.planPrice,
payPrice: res.payPrice,
planProps: {
level: res.nextSubLevel,
mode: res.nextMode
}
});
}
} }
}); });
@@ -136,9 +105,7 @@ const Standard = ({
minH={'550px'} minH={'550px'}
> >
{standardSubList.map((item) => { {standardSubList.map((item) => {
const isCurrentPlan = const isCurrentPlan = item.level === myStandardPlan?.currentSubLevel;
item.level === standardPlan?.currentSubLevel &&
selectSubMode === standardPlan?.currentMode;
return ( return (
<Box <Box
@@ -158,7 +125,7 @@ const Standard = ({
})} })}
> >
<Box fontSize={'md'} fontWeight={'500'}> <Box fontSize={'md'} fontWeight={'500'}>
{t(item.label as any)} {t(item.label)}
</Box> </Box>
<Box fontSize={['32px', '42px']} fontWeight={'bold'}> <Box fontSize={['32px', '42px']} fontWeight={'bold'}>
{item.price} {item.price}
@@ -166,46 +133,44 @@ const Standard = ({
<Box color={'myGray.500'} h={'40px'} fontSize={'xs'}> <Box color={'myGray.500'} h={'40px'} fontSize={'xs'}>
{t(item.desc as any, { title: feConfigs?.systemTitle })} {t(item.desc as any, { title: feConfigs?.systemTitle })}
</Box> </Box>
{/* Button */}
{(() => { {(() => {
if ( if (item.level === StandardSubLevelEnum.free) {
item.level === StandardSubLevelEnum.free &&
selectSubMode === SubModeEnum.year
) {
return ( return (
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}> <Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
{t('common:support.wallet.subscription.Nonsupport')} {t('common:support.wallet.subscription.Nonsupport')}
</Button> </Button>
); );
} }
if ( // feature:
item.level === standardPlan?.nextSubLevel && // if (
selectSubMode === standardPlan?.nextMode // item.level === myStandardPlan?.nextSubLevel &&
) { // selectSubMode === myStandardPlan?.nextMode
return ( // ) {
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled> // return (
{t('common:support.wallet.subscription.Next plan')} // <Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
</Button> // {t('common:support.wallet.subscription.Next plan')}
); // </Button>
} // );
// }
if (isCurrentPlan) { if (isCurrentPlan) {
return ( return (
<Button <Button
mt={4} mt={4}
mb={6} mb={6}
w={'100%'} w={'100%'}
variant={'whiteBase'} variant={'primary'}
isDisabled={ isLoading={isLoading}
item.level === standardPlan?.nextSubLevel && onClick={() => {
selectSubMode === standardPlan?.nextMode onPay({
} type: BillTypeEnum.standSubPlan,
onClick={() =>
onclickPreCheckStandPlan({
level: item.level, level: item.level,
mode: selectSubMode subMode: selectSubMode
}) });
} }}
> >
{t('common:support.wallet.subscription.Current plan')} {t('user:bill.renew_plan')}
</Button> </Button>
); );
} }
@@ -216,11 +181,12 @@ const Standard = ({
mb={6} mb={6}
w={'100%'} w={'100%'}
variant={'primaryGhost'} variant={'primaryGhost'}
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan} isLoading={isLoading}
onClick={() => onClick={() =>
onclickPreCheckStandPlan({ onPay({
type: BillTypeEnum.standSubPlan,
level: item.level, level: item.level,
mode: selectSubMode subMode: selectSubMode
}) })
} }
> >
@@ -236,13 +202,7 @@ const Standard = ({
})} })}
</Grid> </Grid>
{!!confirmPayData && ( {!!qrPayData && <QRCodePayModal tip="您正在购买订阅套餐" {...qrPayData} />}
<ConfirmPayModal
{...confirmPayData}
onClose={() => setConfirmPayData(undefined)}
onConfirmPay={() => onclickUpdateStandardPlan(confirmPayData.planProps)}
/>
)}
</Flex> </Flex>
); );
}; };
@@ -301,100 +261,3 @@ const RowTabs = ({
</Box> </Box>
); );
}; };
const ConfirmPayModal = ({
teamBalance,
totalPrice,
payPrice,
onClose,
onConfirmPay
}: ConfirmPayModalProps & { onClose: () => void; onConfirmPay: () => void }) => {
const { t } = useTranslation();
const [qrPayData, setQRPayData] = useState<QRPayProps>();
const formatPayPrice = Math.ceil(formatStorePrice2Read(payPrice));
const formatTeamBalance = Math.floor(formatStorePrice2Read(teamBalance));
const { mutate: handleClickPay, isLoading } = useRequest({
mutationFn: async (amount: number) => {
// 获取支付二维码
return getWxPayQRCode({
type: BillTypeEnum.balance,
balance: amount
});
},
onSuccess(res) {
setQRPayData({
readPrice: res.readPrice,
codeUrl: res.codeUrl,
billId: res.billId
});
}
});
const { runAsync: onPay, loading: onPaying } = useRequest2(async () => onConfirmPay());
return (
<MyModal
isOpen
iconSrc="modal/confirmPay"
title={t('common:support.wallet.Confirm pay')}
onClose={onClose}
>
<ModalBody py={5} px={9}>
<Flex>
<Box flex={'0 0 100px'}>{t('common:pay.new_package_price')}</Box>
<Box>{t('common:pay.yuan', { amount: formatStorePrice2Read(totalPrice) })}</Box>
</Flex>
<Flex mt={6}>
<Box flex={'0 0 100px'}>{t('common:pay.old_package_price')}</Box>
<Box>
{t('common:pay.yuan', {
amount: Math.floor(formatStorePrice2Read(totalPrice - payPrice))
})}
</Box>
</Flex>
<Flex mt={6}>
<Box flex={'0 0 100px'}>{t('common:pay.need_to_pay')}</Box>
<Box>
{t('common:pay.yuan', {
amount: formatPayPrice
})}
</Box>
</Flex>
</ModalBody>
<ModalFooter
borderTopWidth={'1px'}
borderTopColor={'borderColor.base'}
mx={9}
justifyContent={'flex-start'}
px={0}
>
<Box>{t('common:pay.balance') + ': '}</Box>
<Box ml={2} flex={1}>
{t('common:pay.yuan', {
amount: formatTeamBalance
})}
</Box>
{teamBalance >= payPrice ? (
<Button isLoading={onPaying} size={'sm'} onClick={onPay}>
{t('common:pay.confirm_pay')}
</Button>
) : (
<Button
size={'sm'}
isLoading={isLoading}
onClick={() => {
handleClickPay(Math.ceil(formatStorePrice2Read(payPrice - teamBalance)));
}}
>
{t('common:pay.to_recharge')}
</Button>
)}
</ModalFooter>
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onConfirmPay} />}
</MyModal>
);
};

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { Box, Image } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { getTeamPlanStatus } from '@/web/support/user/team/api'; import { getTeamPlanStatus } from '@/web/support/user/team/api';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@@ -14,7 +13,6 @@ import { getToken } from '@/web/support/user/auth';
import Script from 'next/script'; import Script from 'next/script';
const PriceBox = () => { const PriceBox = () => {
const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery( const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery(
@@ -60,6 +58,6 @@ export default PriceBox;
export async function getServerSideProps(context: any) { export async function getServerSideProps(context: any) {
return { return {
props: { ...(await serviceSideProps(context)) } props: { ...(await serviceSideProps(context, ['user'])) }
}; };
} }

View File

@@ -1,16 +0,0 @@
import { GET, POST, PUT, DELETE } from '@/web/common/api/request';
import {
StandardSubPlanParams,
StandardSubPlanUpdateResponse
} from '@fastgpt/global/support/wallet/sub/api';
import { SubStatusEnum, SubTypeEnum } from '@fastgpt/global/support/wallet/sub/constants';
export const putTeamDatasetSubStatus = (data: {
status: `${SubStatusEnum}`;
type: `${SubTypeEnum}`;
}) => POST('/proApi/support/wallet/sub/updateStatus', data);
export const postCheckStandardSub = (data: StandardSubPlanParams) =>
POST<StandardSubPlanUpdateResponse>('/proApi/support/wallet/sub/standard/preCheck', data);
export const postUpdateStandardSub = (data: StandardSubPlanParams) =>
POST<StandardSubPlanUpdateResponse>('/proApi/support/wallet/sub/standard/update', data);