diff --git a/frontend/src/components/payment/StripePaymentInline.vue b/frontend/src/components/payment/StripePaymentInline.vue index bcbb9bb4ff..15e721cfdd 100644 --- a/frontend/src/components/payment/StripePaymentInline.vue +++ b/frontend/src/components/payment/StripePaymentInline.vue @@ -120,18 +120,31 @@ onUnmounted(() => { async function handlePay() { if (!stripeInstance || !elementsInstance || submitting.value) return - // For popup-based methods (Alipay redirect / WeChat QR): open popup with direct confirm + // For popup-based methods (Alipay redirect / WeChat QR): open lightweight popup if (POPUP_METHODS.has(selectedType.value)) { - const stripeUrl = router.resolve({ - path: '/payment/stripe', + const popupUrl = router.resolve({ + path: '/payment/stripe-popup', query: { order_id: String(props.orderId), - client_secret: props.clientSecret, method: selectedType.value, + amount: String(props.payAmount), }, }).href - window.open(stripeUrl, 'paymentPopup', STRIPE_POPUP_WINDOW_FEATURES) - emit('redirect', props.orderId, stripeUrl) + const popup = window.open(popupUrl, 'paymentPopup', STRIPE_POPUP_WINDOW_FEATURES) + + // Wait for popup ready, then send credentials via postMessage + const onReady = (event: MessageEvent) => { + if (event.source !== popup || event.data?.type !== 'STRIPE_POPUP_READY') return + window.removeEventListener('message', onReady) + popup?.postMessage({ + type: 'STRIPE_POPUP_INIT', + clientSecret: props.clientSecret, + publishableKey: props.publishableKey, + }, window.location.origin) + } + window.addEventListener('message', onReady) + + emit('redirect', props.orderId, popupUrl) return } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 1199ffd5f1..808b65b3fa 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -5164,6 +5164,12 @@ export default { }, stripePay: 'Pay Now', stripeSuccessProcessing: 'Payment successful, processing your order...', + stripePopup: { + redirecting: 'Redirecting to payment page...', + loadingQr: 'Loading WeChat Pay QR code...', + timeout: 'Timed out waiting for payment credentials, please retry', + qrFailed: 'Failed to get WeChat Pay QR code', + }, subscribeNow: 'Subscribe Now', planFeatures: 'Features', planCard: { diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index d56065759d..d4ed66247e 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -5361,6 +5361,12 @@ export default { }, stripePay: '立即支付', stripeSuccessProcessing: '支付成功,正在处理订单...', + stripePopup: { + redirecting: '正在跳转到支付页面...', + loadingQr: '正在获取微信支付二维码...', + timeout: '等待支付凭证超时,请重试', + qrFailed: '未能获取微信支付二维码', + }, subscribeNow: '立即开通', planFeatures: '功能特性', planCard: { diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index bec2d7b642..899b8f0736 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -250,6 +250,17 @@ const routes: RouteRecordRaw[] = [ requiresPayment: true } }, + { + path: '/payment/stripe-popup', + name: 'StripePopup', + component: () => import('@/views/user/StripePopupView.vue'), + meta: { + requiresAuth: true, + requiresAdmin: false, + title: 'Payment', + requiresPayment: true + } + }, { path: '/custom/:id', name: 'CustomPage', diff --git a/frontend/src/views/user/StripePopupView.vue b/frontend/src/views/user/StripePopupView.vue new file mode 100644 index 0000000000..440882cdcc --- /dev/null +++ b/frontend/src/views/user/StripePopupView.vue @@ -0,0 +1,154 @@ + + +