diff --git a/frontend/providers/costcenter/public/locales/en/common.json b/frontend/providers/costcenter/public/locales/en/common.json
index d7d9220483d..0cfc42b04d8 100644
--- a/frontend/providers/costcenter/public/locales/en/common.json
+++ b/frontend/providers/costcenter/public/locales/en/common.json
@@ -241,5 +241,6 @@
"first_recharge_title": "Get Double with Initial Purchase!",
"credit_purchase": "Credit Purchase",
"remaining_balance": "Remaining Balance",
- "custom_amount": "Custom Amount"
+ "custom_amount": "Custom Amount",
+ "pay with alipay": "Pay With Alipay"
}
diff --git a/frontend/providers/costcenter/public/locales/zh/common.json b/frontend/providers/costcenter/public/locales/zh/common.json
index 0bca7f2394f..a8f558ebf17 100644
--- a/frontend/providers/costcenter/public/locales/zh/common.json
+++ b/frontend/providers/costcenter/public/locales/zh/common.json
@@ -241,5 +241,6 @@
"first_recharge_title": "首充双倍!",
"credit_purchase": "余额充值",
"remaining_balance": "当前余额",
- "custom_amount": "输入金额"
+ "custom_amount": "输入金额",
+ "pay with alipay": "支付宝支付"
}
diff --git a/frontend/providers/costcenter/src/assert/alipay.svg b/frontend/providers/costcenter/src/assert/alipay.svg
new file mode 100644
index 00000000000..e609b3aa20a
--- /dev/null
+++ b/frontend/providers/costcenter/src/assert/alipay.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/providers/costcenter/src/components/CheckoutForm.tsx b/frontend/providers/costcenter/src/components/CheckoutForm.tsx
deleted file mode 100644
index e99e9bdda16..00000000000
--- a/frontend/providers/costcenter/src/components/CheckoutForm.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Spinner } from '@chakra-ui/react';
-// import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';
-import useEnvStore from '@/stores/env';
-import { FormEvent } from 'react';
-const CheckoutForm = (props: { url: string; sessionId: string }) => {
- // const stripe = useStripe();
- // const elements = useElements();
-
- const stripePromise = useEnvStore((s) => s.stripePromise);
- const handleSubmit = async (event: FormEvent) => {
- // We don't want to let default form submission happen here,
- // which would refresh the page.
- event.preventDefault();
- if (!stripePromise) {
- // Stripe.js hasn't yet loaded.
- // Make sure to disable form submission until Stripe.js has loaded.
- return;
- }
- const res1 = await stripePromise;
- if (!res1) return;
- const res = await res1.redirectToCheckout({
- sessionId: props.sessionId
- });
- };
-
- return ;
-};
-
-export default CheckoutForm;
diff --git a/frontend/providers/costcenter/src/components/RechargeModal.tsx b/frontend/providers/costcenter/src/components/RechargeModal.tsx
deleted file mode 100644
index 003a048b30b..00000000000
--- a/frontend/providers/costcenter/src/components/RechargeModal.tsx
+++ /dev/null
@@ -1,742 +0,0 @@
-import vector from '@/assert/Vector.svg';
-import stripe_icon from '@/assert/bi_stripe.svg';
-import wechat_icon from '@/assert/ic_baseline-wechat.svg';
-import CurrencySymbol from '@/components/CurrencySymbol';
-import OuterLink from '@/components/outerLink';
-import { useCustomToast } from '@/hooks/useCustomToast';
-import useEnvStore from '@/stores/env';
-import useSessionStore from '@/stores/session';
-import { ApiResp } from '@/types/api';
-import { Pay, Payment } from '@/types/payment';
-import { deFormatMoney, formatMoney } from '@/utils/format';
-import {
- Box,
- Button,
- Flex,
- Img,
- Modal,
- ModalCloseButton,
- ModalContent,
- ModalHeader,
- ModalOverlay,
- NumberDecrementStepper,
- NumberIncrementStepper,
- NumberInput,
- NumberInputField,
- NumberInputStepper,
- SimpleGrid,
- Spinner,
- Text,
- useDisclosure
-} from '@chakra-ui/react';
-import { MyTooltip } from '@sealos/ui';
-import { Stripe } from '@stripe/stripe-js';
-import { useMutation, useQuery } from '@tanstack/react-query';
-import type { AxiosInstance } from 'axios';
-import { isNumber } from 'lodash';
-import { useTranslation } from 'next-i18next';
-import { QRCodeSVG } from 'qrcode.react';
-import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
-import GiftIcon from './icons/GiftIcon';
-import HelpIcon from './icons/HelpIcon';
-const StripeForm = (props: {
- tradeNO?: string;
- complete: number;
- stripePromise: Promise;
-}) => {
- const stripePromise = props.stripePromise;
- const sessionId = props.tradeNO;
- const complete = props.complete;
- useEffect(() => {
- if (stripePromise && sessionId)
- (async () => {
- try {
- const res1 = await stripePromise;
- if (!res1) return;
- if (complete !== 2) return;
- await res1.redirectToCheckout({
- sessionId
- });
- } catch (e) {
- console.error(e);
- }
- })();
- }, [sessionId, stripePromise, complete]);
- return (
-
-
-
- );
-};
-
-function WechatPayment(props: { complete: number; codeURL?: string; tradeNO?: string }) {
- const { t } = useTranslation();
- return (
-
-
-
- {t('Scan with WeChat')}
-
- {props.complete === 2 && !!props.codeURL ? (
-
- ) : (
- waiting...
- )}
-
-
- {t('Order Number')}: {props.tradeNO || ''}
-
-
- {t('Payment Result')}:{props.complete === 3 ? t('Payment Successful') : t('In Payment')}
-
-
-
-
- );
-}
-const BonusBox = (props: {
- onClick: () => void;
- selected: boolean;
- bouns: number;
- isFirst?: boolean;
- amount: number;
-}) => {
- const { t } = useTranslation();
- const currency = useEnvStore((s) => s.currency);
-
- return (
- {
- e.preventDefault();
- props.onClick();
- }}
- >
- {props.isFirst ? (
-
-
- {t('Double')}!
-
- +
-
- {props.bouns}
-
-
-
- ) : props.bouns !== 0 ? (
-
- {t('Bonus')}
-
- {props.bouns}
-
- ) : (
- <>>
- )}
-
-
-
- {props.amount}
-
-
-
- );
-};
-const RechargeModal = forwardRef(
- (
- props: {
- onPaySuccess?: () => void;
- onPayError?: () => void;
- onCreatedSuccess?: () => void;
- onCreatedError?: () => void;
- onCancel?: () => void;
- balance: number;
- stripePromise: Promise;
- request: AxiosInstance;
- },
- ref
- ) => {
- const balance = props.balance || 0;
- const { t } = useTranslation();
- const { isOpen, onOpen, onClose: _onClose } = useDisclosure();
- const request = props.request;
- useImperativeHandle(
- ref,
- () => ({
- onOpen: () => {
- onOpen();
- },
- onClose: () => {
- onClose();
- }
- }),
- []
- );
-
- const [step, setStep] = useState(1);
-
- // 整个流程跑通需要状态管理, 0 初始态, 1 创建支付单, 2 支付中, 3 支付成功
- const [complete, setComplete] = useState<0 | 1 | 2 | 3>(0);
- // 0 是微信,1 是stripe
- const [payType, setPayType] = useState<'wechat' | 'stripe'>('wechat');
- // 计费详情
- const [detail, setDetail] = useState(false);
- const [paymentName, setPaymentName] = useState('');
- const [selectAmount, setSelectAmount] = useState(0);
- const createPaymentRes = useMutation(
- () =>
- request.post>('/api/account/payment', {
- amount: deFormatMoney(amount),
- paymentMethod: payType
- }),
- {
- onSuccess(data) {
- setPaymentName((data?.data?.paymentName as string).trim());
- props.onCreatedSuccess?.();
- setComplete(2);
- },
- onError(err: any) {
- toast({
- status: 'error',
- title: err?.message || '',
- isClosable: true,
- position: 'top'
- });
- props.onCreatedError?.();
- setComplete(0);
- }
- }
- );
-
- const { data, isPreviousData } = useQuery(
- ['query-charge-res', { id: paymentName }],
- () =>
- request>('/api/account/payment/pay', {
- params: {
- id: paymentName
- }
- }),
- {
- refetchInterval: complete === 2 ? 1000 : false,
- enabled: complete === 2,
- cacheTime: 0,
- staleTime: 0,
- onSuccess(data) {
- setTimeout(() => {
- if ((data?.data?.status || '').toUpperCase() === 'SUCCESS') {
- createPaymentRes.reset();
- setComplete(3);
- props.onPaySuccess?.();
- onClose();
- setComplete(0);
- }
- }, 3000);
- }
- }
- );
- const cancalPay = useCallback(() => {
- createPaymentRes.reset();
- props.onCancel?.();
- setComplete(0);
- }, [createPaymentRes]);
-
- const onClose = () => {
- setDetail(false);
- cancalPay();
- _onClose();
- };
- const { session } = useSessionStore();
- const { toast } = useCustomToast();
- const { data: bonuses, isSuccess } = useQuery(
- ['bonus', session.user.id],
- () =>
- request.post<
- any,
- ApiResp<{
- discount: {
- defaultSteps: Record;
- firstRechargeDiscount: Record;
- };
- }>
- >('/api/price/bonus'),
- {}
- );
-
- const [defaultSteps, ratios, steps, specialBonus] = useMemo(() => {
- const defaultSteps = Object.entries(bonuses?.data?.discount.defaultSteps || {}).sort(
- (a, b) => +a[0] - +b[0]
- );
- const ratios = defaultSteps.map(([key, value]) => value);
- const steps = defaultSteps.map(([key, value]) => +key);
- const specialBonus = Object.entries(bonuses?.data?.discount.firstRechargeDiscount || {}).sort(
- (a, b) => +a[0] - +b[0]
- );
- const temp: number[] = [];
- specialBonus.forEach(([k, v]) => {
- const step = +k;
- if (steps.findIndex((v) => step === v) === -1) {
- temp.push(+k);
- }
- });
- steps.unshift(...temp);
- ratios.unshift(...temp.map(() => 0));
- return [defaultSteps, ratios, steps, specialBonus];
- }, [bonuses?.data?.discount.defaultSteps, bonuses?.data?.discount.firstRechargeDiscount]);
- const [amount, setAmount] = useState(() => 0);
- const getBonus = (amount: number) => {
- let ratio = 0;
- let specialIdx = specialBonus.findIndex(([k]) => +k === amount);
- if (specialIdx >= 0) return Math.floor((amount * specialBonus[specialIdx][1]) / 100);
- const step = [...steps].reverse().findIndex((step) => amount >= step);
- if (ratios.length > step && step > -1) ratio = [...ratios].reverse()[step];
- return Math.floor((amount * ratio) / 100);
- };
- const { stripeEnabled, wechatEnabled } = useEnvStore();
- useEffect(() => {
- if (steps && steps.length > 0) {
- const result = steps.map((v, idx) => [v, getBonus(v), idx]).filter(([k, v]) => v > 0);
- if (result.length > 0) {
- const [key, bouns, idx] = result[0];
- setSelectAmount(idx);
- setAmount(key);
- }
- }
- }, [steps]);
- const handleWechatConfirm = () => {
- setPayType('wechat');
- setComplete(1);
- createPaymentRes.mutate();
- };
-
- const handleStripeConfirm = () => {
- setPayType('stripe');
- if (amount < 10) {
- toast({
- status: 'error',
- title: t('Pay Minimum Tips')
- });
- // 校检,stripe有最低费用的要求
- return;
- }
- setComplete(1);
- createPaymentRes.mutate();
- };
- const currency = useEnvStore((s) => s.currency);
- return (
-
-
-
- {!detail ? (
- complete === 0 ? (
- <>
-
- {t('credit_purchase')}
-
-
-
-
-
- {t('remaining_balance')}
-
-
-
- {formatMoney(balance).toFixed(2)}
-
-
-
-
-
- {t('Select Amount')}
-
- {specialBonus && specialBonus.length > 0 && (
-
-
-
- {t('first_recharge_title')}
-
-
- {t('first_recharge_tips')}
-
- }
- >
-
-
-
- )}
-
-
- {steps.map((amount, index) => (
- +a[0] === amount) >= 0}
- bouns={getBonus(amount)}
- onClick={() => {
- setSelectAmount(index);
- setAmount(amount);
- }}
- selected={selectAmount === index}
- />
- ))}
-
-
-
-
- {t('custom_amount')}
-
- {
- const maxAmount = 10_000_000;
- if (!str || !isNumber(v) || isNaN(v)) {
- setAmount(0);
- return;
- }
- if (v > maxAmount) {
- setAmount(maxAmount);
- return;
- }
- setAmount(v);
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('Bonus')}
-
-
-
- {getBonus(amount)}
-
-
- setDetail(true)}
- >
-
-
-
- {stripeEnabled && (
-
- )}
- {wechatEnabled && (
-
- )}
-
-
- >
- ) : (
- <>
-
- {
- cancalPay();
- }}
- >
- {t('Recharge Amount')}
-
-
- {payType === 'wechat' ? (
-
- ) : (
-
- )}
- >
- )
- ) : (
- <>
-
- {t('preferential_rules')}
-
-
-
-
-
- {t('Recharge Amount')}
-
-
- {t('preferential_strength')}
-
- {steps &&
- ratios &&
- steps.length === ratios.length &&
- steps
- .map(
- (step, idx, steps) =>
- [
- step,
- idx < steps.length - 1 ? steps[idx + 1] : undefined,
- ratios[idx]
- ] as const
- )
- .filter(([_, _2, ratio], idx) => {
- return ratio > 0;
- })
-
- .map(([pre, next, ratio], idx) => (
- <>
-
- {pre}
- {' <= '}
- {t('Recharge Amount')}
- {next ? `< ${next}` : ''}
-
-
- {t('Bonus')}
- {ratio.toFixed(2)}%
-
- >
- ))}
- {specialBonus &&
- specialBonus.map(([k, v], i) => (
- <>
-
- {k} = {t('Recharge Amount')}{' '}
-
-
- {t('Bonus')} {v} %
-
- >
- ))}
-
-
- >
- )}
-
-
- );
- }
-);
-
-RechargeModal.displayName = 'RechargeModal';
-
-export default RechargeModal;
diff --git a/frontend/providers/costcenter/src/components/RechargeModal/AlipayForm.tsx b/frontend/providers/costcenter/src/components/RechargeModal/AlipayForm.tsx
new file mode 100644
index 00000000000..bc0bf37cccc
--- /dev/null
+++ b/frontend/providers/costcenter/src/components/RechargeModal/AlipayForm.tsx
@@ -0,0 +1,23 @@
+import { RechargePaymentState } from '@/constants/payment';
+import { Flex, Spinner } from '@chakra-ui/react';
+import { useEffect } from 'react';
+
+const AlipayForm = ({
+ codeURL: url,
+ rechargePhase
+}: {
+ codeURL?: string;
+ rechargePhase: RechargePaymentState;
+}) => {
+ useEffect(() => {
+ if (rechargePhase !== RechargePaymentState.PROCESSING || !url || !window.top) return;
+ window.top.location.replace(url);
+ }, [rechargePhase, url]);
+ return (
+
+
+
+ );
+};
+
+export default AlipayForm;
diff --git a/frontend/providers/costcenter/src/components/RechargeModal/BonusBox.tsx b/frontend/providers/costcenter/src/components/RechargeModal/BonusBox.tsx
new file mode 100644
index 00000000000..cf2e4516638
--- /dev/null
+++ b/frontend/providers/costcenter/src/components/RechargeModal/BonusBox.tsx
@@ -0,0 +1,113 @@
+import useEnvStore from '@/stores/env';
+import { Flex, Text } from '@chakra-ui/react';
+import { useTranslation } from 'next-i18next';
+import CurrencySymbol from '../CurrencySymbol';
+
+const BonusBox = (props: {
+ onClick: () => void;
+ selected: boolean;
+ bouns: number;
+ isFirst?: boolean;
+ amount: number;
+}) => {
+ const { t } = useTranslation();
+ const currency = useEnvStore((s) => s.currency);
+
+ return (
+ {
+ e.preventDefault();
+ props.onClick();
+ }}
+ >
+ {props.isFirst ? (
+
+
+ {t('Double')}!
+
+ +
+
+ {props.bouns}
+
+
+
+ ) : props.bouns !== 0 ? (
+
+ {t('Bonus')}
+
+ {props.bouns}
+
+ ) : (
+ <>>
+ )}
+
+
+
+ {props.amount}
+
+
+
+ );
+};
+export default BonusBox;
diff --git a/frontend/providers/costcenter/src/components/RechargeModal/StripeForm.tsx b/frontend/providers/costcenter/src/components/RechargeModal/StripeForm.tsx
new file mode 100644
index 00000000000..e364f4bd026
--- /dev/null
+++ b/frontend/providers/costcenter/src/components/RechargeModal/StripeForm.tsx
@@ -0,0 +1,37 @@
+import { RechargePaymentState } from '@/constants/payment';
+import { Flex, Spinner } from '@chakra-ui/react';
+import { Stripe } from '@stripe/stripe-js';
+import { useEffect } from 'react';
+
+const StripeForm = ({
+ tradeNO: sessionId,
+ rechargePhase,
+ stripePromise
+}: {
+ tradeNO?: string;
+ rechargePhase: RechargePaymentState;
+ stripePromise: Promise;
+}) => {
+ useEffect(() => {
+ if (stripePromise && sessionId)
+ (async () => {
+ try {
+ const res1 = await stripePromise;
+ if (!res1) return;
+ if (rechargePhase !== RechargePaymentState.PROCESSING) return;
+ await res1.redirectToCheckout({
+ sessionId
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ })();
+ }, [sessionId, stripePromise, rechargePhase]);
+ return (
+
+
+
+ );
+};
+
+export default StripeForm;
diff --git a/frontend/providers/costcenter/src/components/RechargeModal/WechatPayment.tsx b/frontend/providers/costcenter/src/components/RechargeModal/WechatPayment.tsx
new file mode 100644
index 00000000000..24d3ab021cc
--- /dev/null
+++ b/frontend/providers/costcenter/src/components/RechargeModal/WechatPayment.tsx
@@ -0,0 +1,68 @@
+import wechat_icon from '@/assert/ic_baseline-wechat.svg';
+import { RechargePaymentState } from '@/constants/payment';
+import { Box, Flex, Text } from '@chakra-ui/react';
+import { useTranslation } from 'next-i18next';
+import { QRCodeSVG } from 'qrcode.react';
+export default function WechatPayment({
+ rechargePhase,
+ codeURL,
+ tradeNO
+}: {
+ rechargePhase: RechargePaymentState;
+ codeURL?: string;
+ tradeNO?: string;
+}) {
+ const { t } = useTranslation();
+ return (
+
+
+
+ {t('Scan with WeChat')}
+
+ {rechargePhase === RechargePaymentState.PROCESSING && !!codeURL ? (
+
+ ) : (
+ waiting...
+ )}
+
+
+ {t('Order Number')}: {tradeNO || ''}
+
+
+ {t('Payment Result')}:
+ {rechargePhase === RechargePaymentState.SUCCESS
+ ? t('Payment Successful')
+ : t('In Payment')}
+
+
+
+
+ );
+}
diff --git a/frontend/providers/costcenter/src/components/RechargeModal/index.tsx b/frontend/providers/costcenter/src/components/RechargeModal/index.tsx
new file mode 100644
index 00000000000..dfb562e2285
--- /dev/null
+++ b/frontend/providers/costcenter/src/components/RechargeModal/index.tsx
@@ -0,0 +1,592 @@
+import vector from '@/assert/Vector.svg';
+import alipay_icon from '@/assert/alipay.svg';
+import stripe_icon from '@/assert/bi_stripe.svg';
+import wechat_icon from '@/assert/ic_baseline-wechat.svg';
+import CurrencySymbol from '@/components/CurrencySymbol';
+import OuterLink from '@/components/outerLink';
+import { RechargePaymentState, RechargePaymentType } from '@/constants/payment';
+import { useCustomToast } from '@/hooks/useCustomToast';
+import useEnvStore from '@/stores/env';
+import useSessionStore from '@/stores/session';
+import { ApiResp } from '@/types/api';
+import { Pay, Payment, RechargeModalProps, RechargeModalRef } from '@/types/payment';
+import { deFormatMoney, formatMoney } from '@/utils/format';
+import {
+ Box,
+ Button,
+ ButtonGroup,
+ ButtonGroupProps,
+ Flex,
+ Img,
+ Modal,
+ ModalCloseButton,
+ ModalContent,
+ ModalHeader,
+ ModalOverlay,
+ NumberDecrementStepper,
+ NumberIncrementStepper,
+ NumberInput,
+ NumberInputField,
+ NumberInputStepper,
+ SimpleGrid,
+ Text,
+ useDisclosure
+} from '@chakra-ui/react';
+import { MyTooltip } from '@sealos/ui';
+import { useMutation, useQuery } from '@tanstack/react-query';
+import { isNumber } from 'lodash';
+import { useTranslation } from 'next-i18next';
+import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
+import GiftIcon from '../icons/GiftIcon';
+import HelpIcon from '../icons/HelpIcon';
+import AlipayForm from './AlipayForm';
+import BonusBox from './BonusBox';
+import StripeForm from './StripeForm';
+import WechatPayment from './WechatPayment';
+
+const RechargeModal = forwardRef((props, ref) => {
+ const { t } = useTranslation();
+ const { isOpen, onOpen, onClose: _onClose } = useDisclosure();
+ const { session } = useSessionStore();
+ const { toast } = useCustomToast();
+ const { stripeEnabled, wechatEnabled, alipayEnabled, currency } = useEnvStore();
+ const enabledPaymentMethodsCount = [stripeEnabled, wechatEnabled, alipayEnabled].filter(
+ (x) => !!x
+ ).length;
+ const paymentButtonVarint: ButtonGroupProps['variant'] =
+ enabledPaymentMethodsCount <= 2 ? 'solid' : 'outline';
+ const request = props.request;
+ const balance = props.balance || 0;
+ const [step, setStep] = useState(1);
+ const [payType, setPayType] = useState(RechargePaymentType.Wechat);
+ const [detail, setDetail] = useState(false);
+ const [paymentName, setPaymentName] = useState('');
+ const [selectAmount, setSelectAmount] = useState(0);
+ const [amount, setAmount] = useState(0);
+ const [recharegPhase, setRecharegPhase] = useState(
+ RechargePaymentState.IDLE
+ );
+ useImperativeHandle(
+ ref,
+ () => ({
+ onOpen: () => {
+ onOpen();
+ },
+ onClose: () => {
+ onClose();
+ }
+ }),
+ []
+ );
+
+ const createPaymentMutation = useMutation(
+ () =>
+ request.post>('/api/account/payment', {
+ amount: deFormatMoney(amount),
+ paymentMethod: payType
+ }),
+ {
+ onSuccess(data) {
+ setPaymentName((data?.data?.paymentName as string).trim());
+ props.onCreatedSuccess?.();
+ setRecharegPhase(RechargePaymentState.PROCESSING);
+ },
+ onError(err: any) {
+ toast({
+ status: 'error',
+ title: err?.message || '',
+ isClosable: true,
+ position: 'top'
+ });
+ props.onCreatedError?.();
+ setRecharegPhase(RechargePaymentState.IDLE);
+ }
+ }
+ );
+ const { data, isPreviousData } = useQuery(
+ ['query-charge-res', { id: paymentName }],
+ () =>
+ request>('/api/account/payment/pay', {
+ params: {
+ id: paymentName
+ }
+ }),
+ {
+ refetchInterval: recharegPhase === RechargePaymentState.PROCESSING ? 1000 : false,
+ enabled: recharegPhase === RechargePaymentState.PROCESSING,
+ cacheTime: 0,
+ staleTime: 0,
+ onSuccess(data) {
+ setTimeout(() => {
+ if ((data?.data?.status || '').toUpperCase() === 'SUCCESS') {
+ createPaymentMutation.reset();
+ setRecharegPhase(RechargePaymentState.SUCCESS);
+ props.onPaySuccess?.();
+ onClose();
+ setRecharegPhase(RechargePaymentState.IDLE);
+ }
+ }, 3000);
+ }
+ }
+ );
+ const cancalPay = () => {
+ createPaymentMutation.reset();
+ props.onCancel?.();
+ setRecharegPhase(RechargePaymentState.IDLE);
+ };
+ const onClose = () => {
+ setDetail(false);
+ cancalPay();
+ _onClose();
+ };
+
+ const { data: bonuses, isSuccess } = useQuery(
+ ['bonus', session.user.id],
+ () =>
+ request.post<
+ any,
+ ApiResp<{
+ discount: {
+ defaultSteps: Record;
+ firstRechargeDiscount: Record;
+ };
+ }>
+ >('/api/price/bonus'),
+ {}
+ );
+ const [defaultSteps, ratios, steps, specialBonus] = useMemo(() => {
+ const defaultSteps = Object.entries(bonuses?.data?.discount.defaultSteps || {}).sort(
+ (a, b) => +a[0] - +b[0]
+ );
+ const ratios = defaultSteps.map(([key, value]) => value);
+ const steps = defaultSteps.map(([key, value]) => +key);
+ const specialBonus = Object.entries(bonuses?.data?.discount.firstRechargeDiscount || {}).sort(
+ (a, b) => +a[0] - +b[0]
+ );
+ const temp: number[] = [];
+ specialBonus.forEach(([k, v]) => {
+ const step = +k;
+ if (steps.findIndex((v) => step === v) === -1) {
+ temp.push(+k);
+ }
+ });
+ steps.unshift(...temp);
+ ratios.unshift(...temp.map(() => 0));
+ return [defaultSteps, ratios, steps, specialBonus];
+ }, [bonuses?.data?.discount.defaultSteps, bonuses?.data?.discount.firstRechargeDiscount]);
+ const getBonus = (amount: number) => {
+ let ratio = 0;
+ let specialIdx = specialBonus.findIndex(([k]) => +k === amount);
+ if (specialIdx >= 0) return Math.floor((amount * specialBonus[specialIdx][1]) / 100);
+ const step = [...steps].reverse().findIndex((step) => amount >= step);
+ if (ratios.length > step && step > -1) ratio = [...ratios].reverse()[step];
+ return Math.floor((amount * ratio) / 100);
+ };
+ const handleWechatConfirm = () => {
+ setPayType(RechargePaymentType.Wechat);
+ setRecharegPhase(RechargePaymentState.CREATING);
+ createPaymentMutation.mutate();
+ };
+ const handleStripeConfirm = () => {
+ setPayType(RechargePaymentType.Stripe);
+ if (amount < 10) {
+ toast({
+ status: 'error',
+ title: t('Pay Minimum Tips')
+ });
+ // 校检,stripe有最低费用的要求
+ return;
+ }
+ setRecharegPhase(RechargePaymentState.CREATING);
+ createPaymentMutation.mutate();
+ };
+ const handleAlipayConfirm = () => {
+ setPayType(RechargePaymentType.Alipay);
+ setRecharegPhase(RechargePaymentState.CREATING);
+ createPaymentMutation.mutate();
+ };
+ useEffect(() => {
+ if (steps && steps.length > 0) {
+ const result = steps.map((v, idx) => [v, getBonus(v), idx]).filter(([k, v]) => v > 0);
+ if (result.length > 0) {
+ const [key, bouns, idx] = result[0];
+ setSelectAmount(idx);
+ setAmount(key);
+ }
+ }
+ }, [steps]);
+ return (
+
+
+
+ {!detail ? (
+ RechargePaymentState.IDLE === recharegPhase ? (
+ <>
+
+ {t('credit_purchase')}
+
+
+
+
+
+ {t('remaining_balance')}
+
+
+
+ {formatMoney(balance).toFixed(2)}
+
+
+
+
+
+ {t('Select Amount')}
+
+ {specialBonus && specialBonus.length > 0 && (
+
+
+
+ {t('first_recharge_title')}
+
+
+ {t('first_recharge_tips')}
+
+ }
+ >
+
+
+
+ )}
+
+
+ {steps.map((amount, index) => (
+ +a[0] === amount) >= 0}
+ bouns={getBonus(amount)}
+ onClick={() => {
+ setSelectAmount(index);
+ setAmount(amount);
+ }}
+ selected={selectAmount === index}
+ />
+ ))}
+
+
+
+
+ {t('custom_amount')}
+
+ {
+ const maxAmount = 10_000_000;
+ if (!str || !isNumber(v) || isNaN(v)) {
+ setAmount(0);
+ return;
+ }
+ if (v > maxAmount) {
+ setAmount(maxAmount);
+ return;
+ }
+ setAmount(v);
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('Bonus')}
+
+
+
+ {getBonus(amount)}
+
+
+ setDetail(true)}
+ >
+
+
+ Button': {
+ py: '10px',
+ '> img': {
+ boxSize: '20px'
+ }
+ // color: ''
+ },
+ gap: '8px'
+ }
+ : {
+ '> Button': {
+ py: '14px',
+ px: '34px',
+ '> img': {
+ boxSize: '24px'
+ }
+ },
+ gap: '8px'
+ }
+ }
+ >
+ {stripeEnabled && (
+
+ )}
+ {wechatEnabled && (
+
+ )}
+ {alipayEnabled && (
+
+ )}
+
+
+ >
+ ) : (
+ <>
+
+ {
+ cancalPay();
+ }}
+ >
+ {t('Recharge Amount')}
+
+
+ {payType === RechargePaymentType.Wechat ? (
+
+ ) : payType === RechargePaymentType.Stripe ? (
+
+ ) : (
+
+ )}
+ >
+ )
+ ) : (
+ <>
+
+ {t('preferential_rules')}
+
+
+
+
+
+ {t('Recharge Amount')}
+
+
+ {t('preferential_strength')}
+
+ {steps &&
+ ratios &&
+ steps.length === ratios.length &&
+ steps
+ .map(
+ (step, idx, steps) =>
+ [
+ step,
+ idx < steps.length - 1 ? steps[idx + 1] : undefined,
+ ratios[idx]
+ ] as const
+ )
+ .filter(([_, _2, ratio], idx) => {
+ return ratio > 0;
+ })
+
+ .map(([pre, next, ratio], idx) => (
+ <>
+
+ {pre}
+ {' <= '}
+ {t('Recharge Amount')}
+ {next ? `< ${next}` : ''}
+
+
+ {t('Bonus')}
+ {ratio.toFixed(2)}%
+
+ >
+ ))}
+ {specialBonus &&
+ specialBonus.map(([k, v], i) => (
+ <>
+
+ {k} = {t('Recharge Amount')}{' '}
+
+
+ {t('Bonus')} {v} %
+
+ >
+ ))}
+
+
+ >
+ )}
+
+
+ );
+});
+
+RechargeModal.displayName = 'RechargeModal';
+
+export default RechargeModal;
diff --git a/frontend/providers/costcenter/src/components/TransferModal.tsx b/frontend/providers/costcenter/src/components/TransferModal.tsx
index 9f4bf914424..9880555209b 100644
--- a/frontend/providers/costcenter/src/components/TransferModal.tsx
+++ b/frontend/providers/costcenter/src/components/TransferModal.tsx
@@ -2,7 +2,12 @@ import vector from '@/assert/Vector.svg';
import Currencysymbol from '@/components/CurrencySymbol';
import request from '@/service/request';
import useEnvStore from '@/stores/env';
-import { TransferState, transferStatus } from '@/types/Transfer';
+import {
+ TransferModalProps,
+ TransferModalRef,
+ TransferState,
+ transferStatus
+} from '@/types/Transfer';
import { ApiResp } from '@/types/api';
import { deFormatMoney, formatMoney } from '@/utils/format';
import {
@@ -28,260 +33,249 @@ import { useMutation } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { forwardRef, useImperativeHandle, useState } from 'react';
-const TransferModal = forwardRef(
- (
- props: {
- onTransferSuccess?: () => void;
- onTransferError?: () => void;
- onCancel?: () => void;
- balance: number;
- k8s_username: string;
- },
- ref
- ) => {
- useImperativeHandle(
- ref,
- () => ({
- onOpen: () => {
- onOpen();
- },
- onClose: () => {
- onClose();
- }
+const TransferModal = forwardRef((props, ref) => {
+ useImperativeHandle(
+ ref,
+ () => ({
+ onOpen: () => {
+ onOpen();
+ },
+ onClose: () => {
+ onClose();
+ }
+ }),
+ []
+ );
+ const currency = useEnvStore((s) => s.currency);
+ const { t } = useTranslation();
+ const { isOpen, onOpen, onClose: _onClose } = useDisclosure();
+ const [to, setTo] = useState('');
+ const toast = useToast();
+ const balance = props.balance;
+ const mutation = useMutation(
+ () =>
+ request.post>('/api/account/transfer', {
+ amount: deFormatMoney(amount),
+ to
}),
- []
- );
- const currency = useEnvStore((s) => s.currency);
- const { t } = useTranslation();
- const { isOpen, onOpen, onClose: _onClose } = useDisclosure();
- const [to, setTo] = useState('');
- const toast = useToast();
- const balance = props.balance;
- const mutation = useMutation(
- () =>
- request.post>('/api/account/transfer', {
- amount: deFormatMoney(amount),
- to
- }),
- {
- onSuccess(data) {
- mutation.reset();
- setTo('');
- setAmount(0);
- if (data.data?.status.progress === TransferState.TransferStateFailed)
- toast({
- status: 'error',
- title: data.data?.status.reason,
- isClosable: true,
- position: 'top'
- });
- else {
- toast({
- status: 'success',
- title: t('Transfer Success'),
- isClosable: true,
- duration: 2000,
- position: 'top'
- });
- props.onTransferSuccess?.();
- _onClose();
- }
- },
- onError(err: any) {
+ {
+ onSuccess(data) {
+ mutation.reset();
+ setTo('');
+ setAmount(0);
+ if (data.data?.status.progress === TransferState.TransferStateFailed)
toast({
status: 'error',
- title: err?.message || '',
+ title: data.data?.status.reason,
+ isClosable: true,
+ position: 'top'
+ });
+ else {
+ toast({
+ status: 'success',
+ title: t('Transfer Success'),
isClosable: true,
+ duration: 2000,
position: 'top'
});
- props.onTransferError?.();
+ props.onTransferSuccess?.();
+ _onClose();
}
- }
- );
-
- const verify = () => {
- let trim_to = to.trim();
- if (!trim_to || trim_to.length < 6) {
+ },
+ onError(err: any) {
toast({
status: 'error',
- title: t('Recipient ID is invalid'),
+ title: err?.message || '',
isClosable: true,
position: 'top'
});
- return false;
- }
- if (
- (trim_to !== props.k8s_username && trim_to.replace('ns-', '') === props.k8s_username) ||
- trim_to === props.k8s_username
- ) {
- toast({
- status: 'error',
- title: t('The payee cannot be oneself'),
- isClosable: true,
- position: 'top'
- });
- return false;
- }
- // amount 必须是整数
- if (!Number.isInteger(amount)) {
- toast({
- status: 'error',
- title: t('Transfer Amount must be a integer'),
- isClosable: true,
- position: 'top'
- });
- return false;
- }
- if (deFormatMoney(amount + 10) > balance) {
- toast({
- status: 'error',
- title: t('Transfer Amount must be less than balance'),
- isClosable: true,
- position: 'top'
- });
- return false;
+ props.onTransferError?.();
}
+ }
+ );
+
+ const verify = () => {
+ let trim_to = to.trim();
+ if (!trim_to || trim_to.length < 6) {
+ toast({
+ status: 'error',
+ title: t('Recipient ID is invalid'),
+ isClosable: true,
+ position: 'top'
+ });
+ return false;
+ }
+ if (
+ (trim_to !== props.k8s_username && trim_to.replace('ns-', '') === props.k8s_username) ||
+ trim_to === props.k8s_username
+ ) {
+ toast({
+ status: 'error',
+ title: t('The payee cannot be oneself'),
+ isClosable: true,
+ position: 'top'
+ });
+ return false;
+ }
+ // amount 必须是整数
+ if (!Number.isInteger(amount)) {
+ toast({
+ status: 'error',
+ title: t('Transfer Amount must be a integer'),
+ isClosable: true,
+ position: 'top'
+ });
+ return false;
+ }
+ if (deFormatMoney(amount + 10) > balance) {
+ toast({
+ status: 'error',
+ title: t('Transfer Amount must be less than balance'),
+ isClosable: true,
+ position: 'top'
+ });
+ return false;
+ }
- return true;
- };
- const onClose = () => {
- _onClose();
- };
+ return true;
+ };
+ const onClose = () => {
+ _onClose();
+ };
- const [amount, setAmount] = useState(() => 0);
+ const [amount, setAmount] = useState(() => 0);
- const handleConfirm = () => {
- if (!verify()) return;
- mutation.mutate();
- };
- return (
-
-
-
- {
+ if (!verify()) return;
+ mutation.mutate();
+ };
+ return (
+
+
+
+
+ {t('Transfer')}
+
+
+
+
- {t('Transfer')}
-
-
-
+ {
+ e.preventDefault();
+ setTo(e.target.value.trim());
+ }}
+ isDisabled={mutation.isLoading}
+ />
+
-
- {t('Recipient ID')}
+ {t('Transfer Amount')}
+
+ (str.trim() ? setAmount(v) : setAmount(0))}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t('Balance')}
- {
- e.preventDefault();
- setTo(e.target.value.trim());
- }}
- isDisabled={mutation.isLoading}
- />
-
- {t('Transfer Amount')}
+
+
+ {formatMoney(balance).toFixed(2)}
- (str.trim() ? setAmount(v) : setAmount(0))}
+
-
-
- {t('Balance')}
-
-
-
- {formatMoney(balance).toFixed(2)}
-
-
-
+ {t('Transfer')}
+
-
-
- );
- }
-);
+
+
+
+ );
+});
TransferModal.displayName = 'TransferModal';
export default TransferModal;
diff --git a/frontend/providers/costcenter/src/components/cost_overview/components/user.tsx b/frontend/providers/costcenter/src/components/cost_overview/components/user.tsx
index 18ef0c2d361..32a33cb4eba 100644
--- a/frontend/providers/costcenter/src/components/cost_overview/components/user.tsx
+++ b/frontend/providers/costcenter/src/components/cost_overview/components/user.tsx
@@ -5,14 +5,15 @@ import { Box, Button, Center, Flex, Image, Stack, SystemStyleObject, Text } from
import { useQueryClient } from '@tanstack/react-query';
import CurrencySymbol from '@/components/CurrencySymbol';
+import RechargeModal from '@/components/RechargeModal';
+import TransferModal from '@/components/TransferModal';
import { RechargeContext } from '@/pages/cost_overview';
import useEnvStore from '@/stores/env';
import useOverviewStore from '@/stores/overview';
+import { TransferModalRef } from '@/types';
import jsyaml from 'js-yaml';
import { useTranslation } from 'next-i18next';
import { memo, useContext, useEffect, useMemo, useRef } from 'react';
-import RechargeModal from '../../RechargeModal';
-import TransferModal from '../../TransferModal';
export default memo(function UserCard({ balance }: { balance: number }) {
const getSession = useSessionStore((state) => state.getSession);
@@ -34,7 +35,7 @@ export default memo(function UserCard({ balance }: { balance: number }) {
const session = useSessionStore().getSession();
const rechargeRef = useContext(RechargeContext).rechargeRef;
- const transferRef = useRef();
+ const transferRef = useRef(null);
const queryClient = useQueryClient();
useEffect(() => {
// 加锁
@@ -114,7 +115,7 @@ export default memo(function UserCard({ balance }: { balance: number }) {
color="black"
onClick={(e) => {
e.preventDefault();
- transferRef?.current!.onOpen();
+ transferRef?.current?.onOpen();
}}
>
{t('Transfer')}
diff --git a/frontend/providers/costcenter/src/constants/payment.ts b/frontend/providers/costcenter/src/constants/payment.ts
index 316c30034ba..4c242762415 100644
--- a/frontend/providers/costcenter/src/constants/payment.ts
+++ b/frontend/providers/costcenter/src/constants/payment.ts
@@ -78,7 +78,17 @@ export const generateTransferCrd = ({
return '';
}
};
-
+export enum RechargePaymentState {
+ IDLE = 0,
+ CREATING = 1,
+ PROCESSING = 2,
+ SUCCESS = 3
+}
+export enum RechargePaymentType {
+ Wechat = 'wechat',
+ Stripe = 'stripe',
+ Alipay = 'alipay'
+}
// years mock data
export const INIT_YEAR = 2022;
export const CURRENT_MONTH = '本月';
diff --git a/frontend/providers/costcenter/src/pages/_app.tsx b/frontend/providers/costcenter/src/pages/_app.tsx
index 184a002bb54..b641e5f68e8 100644
--- a/frontend/providers/costcenter/src/pages/_app.tsx
+++ b/frontend/providers/costcenter/src/pages/_app.tsx
@@ -1,23 +1,23 @@
import Layout from '@/layout';
-import { sealosApp } from 'sealos-desktop-sdk/app';
-import { EVENT_NAME } from 'sealos-desktop-sdk';
-import '@/styles/globals.scss';
+import { Response as initDataRes } from '@/pages/api/platform/getAppConfig';
+import request from '@/service/request';
+import useAppTypeStore from '@/stores/appType';
+import useBillingStore from '@/stores/billing';
+import useEnvStore from '@/stores/env';
import { theme } from '@/styles/chakraTheme';
+import '@/styles/globals.scss';
+import { ApiResp } from '@/types/api';
import { ChakraProvider } from '@chakra-ui/react';
-import { Hydrate, QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
+import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { appWithTranslation } from 'next-i18next';
import type { AppProps } from 'next/app';
import Router, { useRouter } from 'next/router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
-import 'react-day-picker/dist/style.css';
-import { appWithTranslation, i18n } from 'next-i18next';
import { useEffect } from 'react';
-import request from '@/service/request';
-import { ApiResp } from '@/types/api';
-import { Response as initDataRes } from '@/pages/api/platform/getAppConfig';
-import useEnvStore from '@/stores/env';
-import useAppTypeStore from '@/stores/appType';
-import useBillingStore from '@/stores/billing';
+import 'react-day-picker/dist/style.css';
+import { EVENT_NAME } from 'sealos-desktop-sdk';
+import { sealosApp } from 'sealos-desktop-sdk/app';
// Make sure to call `loadStripe` outside a component’s render to avoid
// recreating the `Stripe` object on every render.
@@ -71,6 +71,7 @@ const App = ({ Component, pageProps }: AppProps) => {
state.setEnv('stripeEnabled', stripeE);
stripeE && state.setStripe(data?.STRIPE_PUB || '');
state.setEnv('wechatEnabled', !!data?.WECHAT_ENABLED);
+ state.setEnv('alipayEnabled', !!data?.ALIPAY_ENABLED);
} catch (error) {
console.error('get init config error');
}
diff --git a/frontend/providers/costcenter/src/pages/api/platform/getAppConfig.ts b/frontend/providers/costcenter/src/pages/api/platform/getAppConfig.ts
index 024d627bba0..cd205807ef1 100644
--- a/frontend/providers/costcenter/src/pages/api/platform/getAppConfig.ts
+++ b/frontend/providers/costcenter/src/pages/api/platform/getAppConfig.ts
@@ -1,8 +1,8 @@
-import type { NextApiRequest, NextApiResponse } from 'next';
-import * as yaml from 'js-yaml';
-import { readFileSync } from 'fs';
import { jsonRes } from '@/service/backend/response';
import { AppConfigType, DefaultAppConfig } from '@/types/config';
+import { readFileSync } from 'fs';
+import * as yaml from 'js-yaml';
+import type { NextApiRequest, NextApiResponse } from 'next';
export type Response = {
REALNAME_RECHARGE_LIMIT: boolean;
@@ -11,6 +11,7 @@ export type Response = {
STRIPE_ENABLED: boolean;
STRIPE_PUB: string;
WECHAT_ENABLED: boolean;
+ ALIPAY_ENABLED: boolean;
CURRENCY: 'shellCoin' | 'cny' | 'usd';
INVOICE_ENABLED: boolean;
GPU_ENABLED: boolean;
@@ -50,6 +51,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
RECHARGE_ENABLED: global.AppConfig.costCenter.recharge.enabled,
TRANSFER_ENABLED: global.AppConfig.costCenter.transferEnabled,
STRIPE_ENABLED: global.AppConfig.costCenter.recharge.payMethods.stripe.enabled,
+ ALIPAY_ENABLED: global.AppConfig.costCenter.recharge.payMethods.alipay.enabled,
STRIPE_PUB: global.AppConfig.costCenter.recharge.payMethods.stripe.publicKey,
WECHAT_ENABLED: global.AppConfig.costCenter.recharge?.payMethods?.wechat?.enabled || false,
CURRENCY: global.AppConfig.costCenter.currencyType,
diff --git a/frontend/providers/costcenter/src/pages/cost_overview/index.tsx b/frontend/providers/costcenter/src/pages/cost_overview/index.tsx
index 1c97e073046..0109ade5db2 100644
--- a/frontend/providers/costcenter/src/pages/cost_overview/index.tsx
+++ b/frontend/providers/costcenter/src/pages/cost_overview/index.tsx
@@ -7,13 +7,14 @@ import useNotEnough from '@/hooks/useNotEnough';
import request from '@/service/request';
import useOverviewStore from '@/stores/overview';
import { ApiResp } from '@/types';
+import { RechargeModalRef } from '@/types/payment';
import { Box, Flex, useToast } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useRouter } from 'next/router';
-import { MutableRefObject, createContext, useEffect, useRef } from 'react';
-export const RechargeContext = createContext<{ rechargeRef: MutableRefObject | null }>({
+import { RefObject, createContext, useEffect, useRef } from 'react';
+export const RechargeContext = createContext<{ rechargeRef: RefObject | null }>({
rechargeRef: null
});
@@ -51,7 +52,7 @@ function CostOverview() {
}, []);
const { NotEnoughModal } = useNotEnough();
const totast = useToast();
- const rechargeRef = useRef();
+ const rechargeRef = useRef(null);
const { data: balance_raw } = useQuery({
queryKey: ['getAccount'],
queryFn: () =>
diff --git a/frontend/providers/costcenter/src/stores/env.ts b/frontend/providers/costcenter/src/stores/env.ts
index 9f5d9c60512..1f39c1c9a1d 100644
--- a/frontend/providers/costcenter/src/stores/env.ts
+++ b/frontend/providers/costcenter/src/stores/env.ts
@@ -5,6 +5,7 @@ type EnvState = {
rechargeEnabled: boolean;
transferEnabled: boolean;
invoiceEnabled: boolean;
+ alipayEnabled: boolean;
gpuEnabled: boolean;
wechatEnabled: boolean;
stripeEnabled: boolean;
@@ -20,6 +21,7 @@ type EnvState = {
const useEnvStore = create((set, get) => ({
realNameRechargeLimit: false,
+ alipayEnabled: false,
rechargeEnabled: false,
transferEnabled: false,
invoiceEnabled: false,
diff --git a/frontend/providers/costcenter/src/types/Transfer.ts b/frontend/providers/costcenter/src/types/Transfer.ts
index 7bf780c32ce..38fe21e4453 100644
--- a/frontend/providers/costcenter/src/types/Transfer.ts
+++ b/frontend/providers/costcenter/src/types/Transfer.ts
@@ -15,3 +15,14 @@ export type transferStatus = {
to: string;
};
};
+export type TransferModalRef = {
+ onOpen: () => void;
+ onClose: () => void;
+};
+export type TransferModalProps = {
+ onTransferSuccess?: () => void;
+ onTransferError?: () => void;
+ onCancel?: () => void;
+ balance: number;
+ k8s_username: string;
+};
diff --git a/frontend/providers/costcenter/src/types/config.ts b/frontend/providers/costcenter/src/types/config.ts
index d5262f00802..bbc40007f69 100644
--- a/frontend/providers/costcenter/src/types/config.ts
+++ b/frontend/providers/costcenter/src/types/config.ts
@@ -32,6 +32,9 @@ export type PayMethods = {
wechat: {
enabled: boolean;
};
+ alipay: {
+ enabled: boolean;
+ };
stripe: {
enabled: boolean;
publicKey: string;
@@ -111,6 +114,9 @@ export var DefaultAppConfig: AppConfigType = {
wechat: {
enabled: false
},
+ alipay: {
+ enabled: false
+ },
stripe: {
enabled: false,
publicKey: ''
diff --git a/frontend/providers/costcenter/src/types/payment.ts b/frontend/providers/costcenter/src/types/payment.ts
index 54f1456cf96..f6d96a545ed 100644
--- a/frontend/providers/costcenter/src/types/payment.ts
+++ b/frontend/providers/costcenter/src/types/payment.ts
@@ -1,3 +1,6 @@
+import { Stripe } from '@stripe/stripe-js';
+import { AxiosInstance } from 'axios';
+
export type Payment = {
paymentName: string;
extra: {
@@ -16,3 +19,17 @@ export type Pay = {
status?: 'Created' | 'SUCCESS';
tradeNO: string;
};
+export type RechargeModalRef = {
+ onOpen: () => void;
+ onClose: () => void;
+};
+export type RechargeModalProps = {
+ onPaySuccess?: () => void;
+ onPayError?: () => void;
+ onCreatedSuccess?: () => void;
+ onCreatedError?: () => void;
+ onCancel?: () => void;
+ balance: number;
+ stripePromise: Promise;
+ request: AxiosInstance;
+};