diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index d209f9d..33c2b84 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -135,6 +135,15 @@ "subDist.fillRequired": "Please fill in the site name, slug, and payment method", "subDist.selectPayment": "Please select a payment method", "subDist.createFailed": "Failed to create order", + "subDist.paymentPageOpened": "Order created. Please complete payment on the opened payment page.", + "subDist.paymentPageFailed": "Failed to open the payment page. Please try again later or contact the site administrator.", + "subDist.popupBlocked": "The browser blocked the payment window. Please allow pop-ups and try again.", + "subDist.cryptoOrderCreated": "Crypto order created. Please complete the transfer using the details on this page.", + "subDist.confirmingPayment": "Confirming payment result...", + "subDist.paymentPending": "Returned to this page. If you have completed payment, activation will finish after the callback is confirmed. Please refresh shortly.", + "subDist.openedSuccess": "Site activated successfully", + "subDist.alreadyOpenTitle": "Your site is already activated", + "subDist.alreadyOpenDesc": "Site {{name}} has been activated. Use the same account in the main console to continue setup.", "tokens.title": "API Keys", "tokens.subtitle": "Manage your API access tokens", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index e90617d..c8c79da 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -135,6 +135,15 @@ "subDist.fillRequired": "请填写站点名称、标识和支付方式", "subDist.selectPayment": "请选择支付方式", "subDist.createFailed": "创建订单失败", + "subDist.paymentPageOpened": "订单已创建,请在打开的支付页面完成付款", + "subDist.paymentPageFailed": "支付页面打开失败,请稍后重试或联系站点管理员", + "subDist.popupBlocked": "浏览器拦截了支付窗口,请允许弹窗后重试", + "subDist.cryptoOrderCreated": "加密货币订单已创建,请按页面信息完成转账", + "subDist.confirmingPayment": "正在确认支付结果...", + "subDist.paymentPending": "已返回当前页面。若您已完成付款,系统会在回调确认后自动开通,请稍后刷新查看。", + "subDist.openedSuccess": "站点已开通成功", + "subDist.alreadyOpenTitle": "您已开通站点", + "subDist.alreadyOpenDesc": "站点 {{name}} 已完成开通,请使用同一账号进入主站控制台继续配置。", "tokens.title": "API 密钥", "tokens.subtitle": "管理您的 API 访问令牌", diff --git a/src/pages/SubDistributor.jsx b/src/pages/SubDistributor.jsx index 6c6e411..5c9c890 100644 --- a/src/pages/SubDistributor.jsx +++ b/src/pages/SubDistributor.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import toast from 'react-hot-toast'; import { createSubDistributorOrder, getSubDistributorInfo } from '../api'; @@ -9,7 +9,7 @@ import { useSite, useCurrency } from '../context/SiteContext'; function submitEpayForm(resData) { const params = resData.data; const url = resData.url; - if (!params || !url) return; + if (!params || !url) return false; const form = document.createElement('form'); form.action = url; form.method = 'POST'; @@ -27,13 +27,16 @@ function submitEpayForm(resData) { document.body.appendChild(form); form.submit(); document.body.removeChild(form); + return true; } export default function SubDistributor() { const { t } = useTranslation(); - const { user } = useAuth(); + const { user, refreshUser, loading: authLoading } = useAuth(); const { site } = useSite(); const { symbol, fmtCNY } = useCurrency(); + const location = useLocation(); + const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [subInfo, setSubInfo] = useState(null); const [submitting, setSubmitting] = useState(false); @@ -66,6 +69,41 @@ export default function SubDistributor() { () => paymentMethods.find((item) => item.type === form.payment_method), [paymentMethods, form.payment_method] ); + const paymentReturned = useMemo( + () => new URLSearchParams(location.search).get('payment') === 'return', + [location.search] + ); + + useEffect(() => { + if (!paymentReturned || authLoading) return; + + let cancelled = false; + const toastId = 'sub-dist-payment-return'; + + const checkPaymentResult = async () => { + if (!user) { + toast(t('subDist.paymentPending'), { id: toastId }); + navigate('/sub-site', { replace: true }); + return; + } + + toast.loading(t('subDist.confirmingPayment'), { id: toastId }); + const refreshed = await refreshUser({ skipErrorHandler: true }); + if (cancelled) return; + + if (refreshed?.has_distributor) { + toast.success(t('subDist.openedSuccess'), { id: toastId }); + } else { + toast(t('subDist.paymentPending'), { id: toastId }); + } + navigate('/sub-site', { replace: true }); + }; + + checkPaymentResult(); + return () => { + cancelled = true; + }; + }, [authLoading, navigate, paymentReturned, refreshUser, t, user]); const handleSubmit = async (e) => { e.preventDefault(); @@ -89,7 +127,7 @@ export default function SubDistributor() { name: form.name.trim(), slug: form.slug.trim().toLowerCase(), payment_method: form.payment_method, - return_url: `${window.location.origin}/sub-distributor`, + return_url: `${window.location.origin}/sub-site?payment=return`, }; if (form.payment_method === 'crypto') { payload.chain = form.chain; @@ -98,14 +136,26 @@ export default function SubDistributor() { const res = await createSubDistributorOrder(payload); if (res.data.message === 'success') { if (res.data.payment_type === 'stripe' && res.data.data?.pay_link) { - window.open(res.data.data.pay_link, '_blank'); + const opened = window.open(res.data.data.pay_link, '_blank'); + if (opened) { + toast.success(t('subDist.paymentPageOpened')); + } else { + toast.error(t('subDist.popupBlocked')); + } } else if (res.data.payment_type === 'crypto') { setCryptoOrder(res.data.data); + toast.success(t('subDist.cryptoOrderCreated')); } else { - submitEpayForm(res.data); + if (submitEpayForm(res.data)) { + toast.success(t('subDist.paymentPageOpened')); + } else { + toast.error(t('subDist.paymentPageFailed')); + } } } else if (res.data.data) { toast.error(typeof res.data.data === 'string' ? res.data.data : t('subDist.createFailed')); + } else { + toast.error(t('subDist.createFailed')); } } catch (e) { // handled by interceptor @@ -146,6 +196,15 @@ export default function SubDistributor() { {t('subDist.goRegister')} + ) : user?.has_distributor ? ( +
+

{t('subDist.alreadyOpenTitle')}

+

+ {t('subDist.alreadyOpenDesc', { + name: user.distributor_name || user.distributor_slug || t('subDist.defaultSiteName'), + })} +

+
) : (