mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-22 07:30:51 +00:00
fix(payment): restore Stripe popup flow, fix WeChat double QR dialog
Restore popup handling for Stripe Alipay/WeChat (reverts inline-only change). Fix WeChat double QR issue by passing handleActions: false to confirmWechatPayPayment, which prevents Stripe's built-in QR dialog and lets our popup render the QR code directly. Flow after fix: - Stripe Alipay: popup → redirects to Alipay - Stripe WeChat: popup → directly shows QR code (no Stripe dialog) - Stripe Card: inline confirm → success page
This commit is contained in:
@@ -62,12 +62,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import { paymentAPI } from '@/api/payment'
|
||||
import { useAppStore } from '@/stores'
|
||||
import { STRIPE_POPUP_WINDOW_FEATURES } from '@/components/payment/providerConfig'
|
||||
import type { Stripe, StripeElements } from '@stripe/stripe-js'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
|
||||
// Stripe payment methods that open a popup (redirect or QR code)
|
||||
const POPUP_METHODS = new Set(['alipay', 'wechat_pay'])
|
||||
|
||||
const props = defineProps<{
|
||||
orderId: number
|
||||
clientSecret: string
|
||||
@@ -75,9 +80,10 @@ const props = defineProps<{
|
||||
payAmount: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ success: []; done: []; back: [] }>()
|
||||
const emit = defineEmits<{ success: []; done: []; back: []; redirect: [orderId: number, payUrl: string] }>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const stripeMount = ref<HTMLElement | null>(null)
|
||||
@@ -88,6 +94,7 @@ const submitting = ref(false)
|
||||
const cancelling = ref(false)
|
||||
const success = ref(false)
|
||||
const ready = ref(false)
|
||||
const selectedType = ref('')
|
||||
|
||||
let stripeInstance: Stripe | null = null
|
||||
let elementsInstance: StripeElements | null = null
|
||||
@@ -115,6 +122,9 @@ onMounted(async () => {
|
||||
} as Record<string, unknown>)
|
||||
paymentElement.mount(stripeMount.value)
|
||||
paymentElement.on('ready', () => { ready.value = true })
|
||||
paymentElement.on('change', (event: { value: { type: string } }) => {
|
||||
selectedType.value = event.value.type
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
initError.value = extractApiErrorMessage(err, t('payment.stripeLoadFailed'))
|
||||
} finally {
|
||||
@@ -122,13 +132,37 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// All payment methods use the same confirmPayment flow:
|
||||
// - Card: confirms directly, no redirect
|
||||
// - WeChat: Stripe shows QR modal overlay on this page, resolves after scan
|
||||
// - Alipay: Stripe redirects to Alipay page, then back to return_url
|
||||
async function handlePay() {
|
||||
if (!stripeInstance || !elementsInstance || submitting.value) return
|
||||
|
||||
// Alipay / WeChat Pay: open popup for redirect or QR display
|
||||
if (POPUP_METHODS.has(selectedType.value)) {
|
||||
const popupUrl = router.resolve({
|
||||
path: '/payment/stripe-popup',
|
||||
query: {
|
||||
order_id: String(props.orderId),
|
||||
method: selectedType.value,
|
||||
amount: String(props.payAmount),
|
||||
},
|
||||
}).href
|
||||
const popup = window.open(popupUrl, 'paymentPopup', STRIPE_POPUP_WINDOW_FEATURES)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Card / Link: confirm inline
|
||||
submitting.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
@success="onPaymentSuccess"
|
||||
@done="onStripeDone"
|
||||
@back="resetPayment"
|
||||
@redirect="onStripeRedirect"
|
||||
/>
|
||||
</template>
|
||||
<!-- Tab content (select phase) -->
|
||||
@@ -277,6 +278,11 @@ function onStripeDone() {
|
||||
}
|
||||
}
|
||||
|
||||
function onStripeRedirect(orderId: number, payUrl: string) {
|
||||
paymentState.value = { ...paymentState.value, orderId, payUrl, qrCode: '' }
|
||||
paymentPhase.value = 'paying'
|
||||
}
|
||||
|
||||
// All checkout data from single API call
|
||||
const checkout = ref<CheckoutInfoResponse>({
|
||||
methods: {}, global_min: 0, global_max: 0,
|
||||
|
||||
@@ -61,7 +61,6 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
function closeWindow() { window.close() }
|
||||
|
||||
onMounted(() => {
|
||||
// Listen for credentials from parent
|
||||
const handler = (event: MessageEvent) => {
|
||||
if (event.origin !== window.location.origin) return
|
||||
if (event.data?.type !== 'STRIPE_POPUP_INIT') return
|
||||
@@ -70,12 +69,10 @@ onMounted(() => {
|
||||
}
|
||||
window.addEventListener('message', handler)
|
||||
|
||||
// Signal parent that popup is ready
|
||||
if (window.opener) {
|
||||
window.opener.postMessage({ type: 'STRIPE_POPUP_READY' }, window.location.origin)
|
||||
}
|
||||
|
||||
// Timeout
|
||||
setTimeout(() => {
|
||||
if (!error.value && !wechatQrUrl.value && !success.value) {
|
||||
error.value = t('payment.stripePopup.timeout')
|
||||
@@ -100,13 +97,16 @@ async function initStripe(clientSecret: string, publishableKey: string) {
|
||||
const returnUrl = window.location.origin + '/payment/result?order_id=' + orderId + '&status=success'
|
||||
|
||||
if (method === 'alipay') {
|
||||
// Alipay: redirect this popup to Alipay payment page
|
||||
const { error: err } = await stripe.confirmAlipayPayment(clientSecret, { return_url: returnUrl })
|
||||
if (err) error.value = err.message || t('payment.result.failed')
|
||||
} else if (method === 'wechat_pay') {
|
||||
// WeChat: confirm with handleActions=false to prevent Stripe's built-in QR dialog,
|
||||
// then render the QR code directly in this popup
|
||||
hint.value = t('payment.stripePopup.loadingQr')
|
||||
const result = await (stripe as any).confirmWechatPayPayment(clientSecret, {
|
||||
payment_method_options: { wechat_pay: { client: 'web' } },
|
||||
})
|
||||
}, { handleActions: false })
|
||||
if (result.error) {
|
||||
error.value = result.error.message || t('payment.result.failed')
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user