소스 검색

perf: 购买成功反馈优化

BaiLuoYan 4 주 전
부모
커밋
a5a0fc23f0

+ 1 - 0
src/locales/en-US/pages.ts

@@ -64,6 +64,7 @@ export default {
             closeWaiting: 'Close',
             paySuccess: 'Payment successful',
             payFailed: 'Payment failed',
+            planPurchaseSuccess: 'Plan purchased successfully',
         },
     },
 

+ 1 - 0
src/locales/fa-IR/pages.ts

@@ -64,6 +64,7 @@ export default {
             closeWaiting: 'بستن',
             paySuccess: 'پرداخت موفق',
             payFailed: 'پرداخت ناموفق',
+            planPurchaseSuccess: 'خرید پلن با موفقیت انجام شد',
         },
     },
 

+ 1 - 0
src/locales/zh-CN/pages.ts

@@ -61,6 +61,7 @@ export default {
             closeWaiting: '关闭',
             paySuccess: '支付成功',
             payFailed: '支付失败',
+            planPurchaseSuccess: '套餐购买成功',
         },
     },
 

+ 8 - 3
src/pages/pricing/components/OrderSummary/PayQrContent.tsx

@@ -13,22 +13,27 @@ export interface PayQrContentProps {
     orderId: string;
     payUrl: string;
     onClose: () => void;
+    /** 支付成功时调用(刷新用户数据、提示等),随后会调用 onClose */
+    onPaymentSuccess?: () => void | Promise<void>;
 }
 
-export function PayQrContent({ orderId, payUrl, onClose }: PayQrContentProps) {
+export function PayQrContent({ orderId, payUrl, onClose, onPaymentSuccess }: PayQrContentProps) {
     const { t } = useTranslation();
     const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
 
     useEffect(() => {
         const poll = () => {
             fetchPayOrderStatus({ orderId })
-                .then((res) => {
+                .then(async (res) => {
                     const state = res?.data?.orderState;
                     if (state === PayOrderStatus.PAID || state === PayOrderStatus.FAILED) {
                         if (timerRef.current) {
                             clearInterval(timerRef.current);
                             timerRef.current = null;
                         }
+                        if (state === PayOrderStatus.PAID && onPaymentSuccess) {
+                            await Promise.resolve(onPaymentSuccess());
+                        }
                         onClose();
                     }
                 })
@@ -43,7 +48,7 @@ export function PayQrContent({ orderId, payUrl, onClose }: PayQrContentProps) {
                 clearInterval(timerRef.current);
             }
         };
-    }, [orderId, onClose]);
+    }, [orderId, onClose, onPaymentSuccess]);
 
     return (
         <div className="flex flex-col items-center gap-4">

+ 8 - 3
src/pages/pricing/components/OrderSummary/PayWaitingContent.tsx

@@ -10,22 +10,27 @@ const POLL_INTERVAL_MS = 2500;
 export interface PayWaitingContentProps {
     orderId: string;
     onClose: () => void;
+    /** 支付成功时调用(刷新用户数据、提示等),随后会调用 onClose */
+    onPaymentSuccess?: () => void | Promise<void>;
 }
 
-export function PayWaitingContent({ orderId, onClose }: PayWaitingContentProps) {
+export function PayWaitingContent({ orderId, onClose, onPaymentSuccess }: PayWaitingContentProps) {
     const { t } = useTranslation();
     const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
 
     useEffect(() => {
         const poll = () => {
             fetchPayOrderStatus({ orderId })
-                .then((res) => {
+                .then(async (res) => {
                     const state = res?.data?.orderState;
                     if (state === PayOrderStatus.PAID || state === PayOrderStatus.FAILED) {
                         if (timerRef.current) {
                             clearInterval(timerRef.current);
                             timerRef.current = null;
                         }
+                        if (state === PayOrderStatus.PAID && onPaymentSuccess) {
+                            await Promise.resolve(onPaymentSuccess());
+                        }
                         onClose();
                     }
                 })
@@ -40,7 +45,7 @@ export function PayWaitingContent({ orderId, onClose }: PayWaitingContentProps)
                 clearInterval(timerRef.current);
             }
         };
-    }, [orderId, onClose]);
+    }, [orderId, onClose, onPaymentSuccess]);
 
     return (
         <p className="text-white/90 text-sm leading-[1.43]">

+ 23 - 22
src/pages/pricing/useAction.tsx

@@ -3,10 +3,11 @@ import { useCallback } from 'react';
 import { Trans, useTranslation } from 'react-i18next';
 
 import LoginForm from '@/components/LoginForm';
+import { message } from '@/config/request/antdAppInstance';
 import { PayUrlShowType } from '@/defines';
 import { useAppUrls } from '@/hooks/useAppUrls';
 import { dialogModel } from '@/models/dialogModel';
-import { fetchPayOrderCreate } from '@/services/config';
+import { fetchGetUserConfig, fetchPayOrderCreate } from '@/services/config';
 import { getToken } from '@/utils/authUtils';
 import { currentUnixTimestamp } from '@/utils/timeUtils';
 
@@ -37,11 +38,7 @@ function useLoginDialog() {
                             i18nKey="pages.pricing.payFlow.loginPrompt"
                             components={{
                                 linkText: (() => {
-                                    const Wrap = ({
-                                        children,
-                                    }: {
-                                        children?: React.ReactNode;
-                                    }) =>
+                                    const Wrap = ({ children }: { children?: React.ReactNode }) =>
                                         deeplinkUrl ? (
                                             <a
                                                 href={deeplinkUrl}
@@ -57,11 +54,7 @@ function useLoginDialog() {
                                     return <Wrap />;
                                 })(),
                                 downloadLink: (() => {
-                                    const Wrap = ({
-                                        children,
-                                    }: {
-                                        children?: React.ReactNode;
-                                    }) =>
+                                    const Wrap = ({ children }: { children?: React.ReactNode }) =>
                                         downloadUrlByPlatform ? (
                                             <a
                                                 href={downloadUrlByPlatform}
@@ -92,6 +85,12 @@ function useQrPayDialog() {
     const { t } = useTranslation();
     const { openDialog, closeDialog } = dialogModel.useModel();
 
+    const onPaymentSuccess = useCallback(() => {
+        return fetchGetUserConfig({}).then(() => {
+            message.success(t('pages.pricing.payFlow.planPurchaseSuccess'));
+        });
+    }, [t]);
+
     return useCallback(
         (order: PayOrder) => {
             const qrDialogId = openDialog({
@@ -101,16 +100,14 @@ function useQrPayDialog() {
                         orderId={order.orderId}
                         payUrl={order.payUrl}
                         onClose={() => closeDialog(qrDialogId)}
+                        onPaymentSuccess={onPaymentSuccess}
                     />
                 ),
                 buttons: [
                     {
                         label: t('pages.pricing.payFlow.closeWaiting'),
                         variant: 'secondary' as const,
-                        onClick: (
-                            _e: React.MouseEvent<HTMLButtonElement>,
-                            dialogId: string
-                        ) => {
+                        onClick: (_e: React.MouseEvent<HTMLButtonElement>, dialogId: string) => {
                             closeDialog(dialogId);
                         },
                     },
@@ -119,7 +116,7 @@ function useQrPayDialog() {
                 closeable: true,
             });
         },
-        [t, openDialog, closeDialog],
+        [t, openDialog, closeDialog, onPaymentSuccess]
     );
 }
 
@@ -127,6 +124,12 @@ function useJumpToPayDialog() {
     const { t } = useTranslation();
     const { openDialog, closeDialog } = dialogModel.useModel();
 
+    const onPaymentSuccess = useCallback(() => {
+        return fetchGetUserConfig({}).then(() => {
+            message.success(t('pages.pricing.payFlow.planPurchaseSuccess'));
+        });
+    }, [t]);
+
     return useCallback(
         (order: PayOrder) => {
             openDialog({
@@ -140,10 +143,7 @@ function useJumpToPayDialog() {
                     {
                         label: t('pages.pricing.payFlow.goToPayButton'),
                         variant: 'primary' as const,
-                        onClick: (
-                            _e: React.MouseEvent<HTMLButtonElement>,
-                            dialogId: string
-                        ) => {
+                        onClick: (_e: React.MouseEvent<HTMLButtonElement>, dialogId: string) => {
                             window.open(order.payUrl, '_blank');
                             closeDialog(dialogId);
                             const waitId = openDialog({
@@ -152,6 +152,7 @@ function useJumpToPayDialog() {
                                     <PayWaitingContent
                                         orderId={order.orderId}
                                         onClose={() => closeDialog(waitId)}
+                                        onPaymentSuccess={onPaymentSuccess}
                                     />
                                 ),
                                 buttons: [
@@ -175,7 +176,7 @@ function useJumpToPayDialog() {
                 closeable: true,
             });
         },
-        [t, openDialog, closeDialog],
+        [t, openDialog, closeDialog, onPaymentSuccess]
     );
 }
 
@@ -215,7 +216,7 @@ export function useAction(): UseActionReturn {
                 })
                 .catch(() => {});
         },
-        [openLoginDialog, openQrPayDialog, openJumpToPayDialog],
+        [openLoginDialog, openQrPayDialog, openJumpToPayDialog]
     );
 
     return {