mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #7819 from ouchihao/codex/bug-stripe-payment-unmount
fix(payment): stop late Stripe callbacks after leaving the page
This commit is contained in:
@@ -129,6 +129,7 @@ const wechatQrUrl = ref('')
|
||||
const redirecting = ref(false)
|
||||
const showPaymentElement = ref(false)
|
||||
|
||||
let disposed = false
|
||||
let stripeInstance: Stripe | null = null
|
||||
let elementsInstance: StripeElements | null = null
|
||||
let redirectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
@@ -167,6 +168,7 @@ onMounted(async () => {
|
||||
|
||||
const { loadStripe } = await import('@stripe/stripe-js/pure')
|
||||
const stripe = await loadStripe(publishableKey)
|
||||
if (disposed) return
|
||||
if (!stripe) { initError.value = t('payment.stripeLoadFailed'); return }
|
||||
|
||||
stripeInstance = stripe
|
||||
@@ -241,6 +243,7 @@ async function confirmWechatPay(stripe: Stripe, clientSecret: string) {
|
||||
}
|
||||
|
||||
function mountPaymentElement(stripe: Stripe, clientSecret: string) {
|
||||
if (disposed) return
|
||||
const isDark = document.documentElement.classList.contains('dark')
|
||||
const elements = stripe.elements({
|
||||
clientSecret,
|
||||
@@ -284,7 +287,7 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function startPolling() {
|
||||
const orderId = Number(route.query.order_id)
|
||||
if (!orderId) return
|
||||
if (!orderId || disposed) return
|
||||
pollTimer = setInterval(async () => {
|
||||
const o = await paymentStore.pollOrderStatus(orderId)
|
||||
if (!o) return
|
||||
@@ -298,6 +301,7 @@ function startPolling() {
|
||||
}
|
||||
|
||||
function scheduleClose() {
|
||||
if (disposed) return
|
||||
if (window.opener) {
|
||||
redirectTimer = setTimeout(() => { window.close() }, 2000)
|
||||
} else {
|
||||
@@ -308,6 +312,7 @@ function scheduleClose() {
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
disposed = true
|
||||
if (redirectTimer) clearTimeout(redirectTimer)
|
||||
if (pollTimer) clearInterval(pollTimer)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { enableAutoUnmount, flushPromises, shallowMount } from '@vue/test-utils'
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
afterEach(() => { vi.clearAllTimers(); vi.useRealTimers() })
|
||||
|
||||
const routeState = vi.hoisted(() => ({
|
||||
query: {} as Record<string, unknown>,
|
||||
@@ -101,7 +104,7 @@ describe('StripePaymentView', () => {
|
||||
client_secret: 'pi_secret_42',
|
||||
}
|
||||
routerPush.mockReset()
|
||||
getOrder.mockReset()
|
||||
getOrder.mockReset().mockResolvedValue({ data: orderFactory() })
|
||||
paymentStore.config = { stripe_publishable_key: 'pk_test' }
|
||||
paymentStore.fetchConfig.mockReset().mockResolvedValue(undefined)
|
||||
paymentStore.pollOrderStatus.mockReset()
|
||||
@@ -118,6 +121,63 @@ describe('StripePaymentView', () => {
|
||||
window.localStorage.clear()
|
||||
})
|
||||
|
||||
it.each(['alipay', 'wechat_pay', ''])('does not start %s payment after leaving during SDK loading', async (method) => {
|
||||
routeState.query.method = method
|
||||
let resolveSDK!: (value: typeof stripeInstance) => void
|
||||
loadStripe.mockReturnValueOnce(new Promise((resolve) => { resolveSDK = resolve }))
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
expect(loadStripe).toHaveBeenCalledTimes(1)
|
||||
wrapper.unmount()
|
||||
resolveSDK(stripeInstance)
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
expect(stripeInstance.confirmAlipayPayment).not.toHaveBeenCalled()
|
||||
expect(stripeInstance.confirmWechatPayPayment).not.toHaveBeenCalled()
|
||||
expect(stripeInstance.elements).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not restart polling when a WeChat QR result arrives after leaving', async () => {
|
||||
vi.useFakeTimers()
|
||||
routeState.query.method = 'wechat_pay'
|
||||
let resolvePayment!: (value: unknown) => void
|
||||
stripeInstance.confirmWechatPayPayment.mockReturnValueOnce(new Promise((resolve) => { resolvePayment = resolve }))
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
expect(stripeInstance.confirmWechatPayPayment).toHaveBeenCalledTimes(1)
|
||||
wrapper.unmount()
|
||||
resolvePayment({ paymentIntent: { status: 'requires_action', next_action: {
|
||||
wechat_pay_display_qr_code: { image_data_url: 'data:image/png;base64,test' }
|
||||
} } })
|
||||
await flushPromises()
|
||||
await vi.advanceTimersByTimeAsync(3000)
|
||||
expect(paymentStore.pollOrderStatus).not.toHaveBeenCalled()
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
})
|
||||
|
||||
it.each([false, true])('handles a completed poll with page unmounted=%s', async (unmounted) => {
|
||||
vi.useFakeTimers()
|
||||
routeState.query.method = 'wechat_pay'
|
||||
stripeInstance.confirmWechatPayPayment.mockResolvedValueOnce({ paymentIntent: {
|
||||
status: 'requires_action', next_action: { wechat_pay_display_qr_code: { image_data_url: 'data:image/png;base64,test' } }
|
||||
} })
|
||||
let resolvePoll!: (value: PaymentOrder) => void
|
||||
paymentStore.pollOrderStatus.mockReturnValueOnce(new Promise((resolve) => { resolvePoll = resolve }))
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
await vi.advanceTimersByTimeAsync(3000)
|
||||
expect(paymentStore.pollOrderStatus).toHaveBeenCalledTimes(1)
|
||||
if (unmounted) wrapper.unmount()
|
||||
resolvePoll(orderFactory({ status: 'PAID' }))
|
||||
await flushPromises()
|
||||
await vi.advanceTimersByTimeAsync(2000)
|
||||
expect(routerPush).toHaveBeenCalledTimes(unmounted ? 0 : 1)
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
})
|
||||
|
||||
it('本地恢复快照缺失时使用订单接口返回的 Stripe 币种展示金额', async () => {
|
||||
getOrder.mockResolvedValue({
|
||||
data: orderFactory({ currency: 'HKD', pay_amount: 103 }),
|
||||
|
||||
Reference in New Issue
Block a user