fix payment resume result consistency

This commit is contained in:
IanShaw027
2026-04-21 02:08:34 +08:00
parent e12599c1b9
commit a27a7add3d
6 changed files with 264 additions and 14 deletions

View File

@@ -177,6 +177,15 @@ function isPendingStatus(status: string | null | undefined): boolean {
return PENDING_STATUSES.has(normalizeOrderStatus(status))
}
async function resolveOrderFromResumeToken(resumeToken: string): Promise<PaymentOrder | null> {
try {
const result = await paymentAPI.resolveOrderPublicByResumeToken(resumeToken)
return result.data
} catch (_err: unknown) {
return null
}
}
function clearStatusRefreshTimer(): void {
if (statusRefreshTimer !== null) {
clearTimeout(statusRefreshTimer)
@@ -230,15 +239,13 @@ onMounted(async () => {
}
}
if (!order.value && resumeToken) {
try {
const result = await paymentAPI.resolveOrderPublicByResumeToken(resumeToken)
order.value = result.data
if (resumeToken) {
const resolvedOrder = await resolveOrderFromResumeToken(resumeToken)
if (resolvedOrder) {
order.value = resolvedOrder
if (!orderId) {
orderId = result.data.id
orderId = resolvedOrder.id
}
} catch (_err: unknown) {
// Resume token recovery failed; do not trust legacy public out_trade_no fallback.
}
}
@@ -278,12 +285,7 @@ onMounted(async () => {
const refreshOrder = async (): Promise<PaymentOrder | null> => {
if (resumeToken) {
try {
const result = await paymentAPI.resolveOrderPublicByResumeToken(resumeToken)
return result.data
} catch (_err: unknown) {
return null
}
return await resolveOrderFromResumeToken(resumeToken)
}
if (orderId) {

View File

@@ -116,6 +116,58 @@ describe('PaymentResultView', () => {
expect(wrapper.text()).not.toContain('payment.result.failed')
})
it('prefers the public resume-token result over a stale restored DB snapshot', async () => {
routeState.query = {
resume_token: 'resume-authoritative',
order_id: '42',
status: 'success',
}
window.localStorage.setItem(PAYMENT_RECOVERY_STORAGE_KEY, JSON.stringify({
orderId: 42,
amount: 88,
qrCode: '',
expiresAt: '2099-01-01T00:10:00.000Z',
paymentType: 'alipay',
payUrl: 'https://pay.example.com/session/42',
clientSecret: '',
payAmount: 88,
orderType: 'balance',
paymentMode: 'popup',
resumeToken: 'resume-authoritative',
createdAt: Date.UTC(2099, 0, 1, 0, 0, 0),
}))
pollOrderStatus.mockResolvedValue({
...orderFactory('PENDING'),
amount: 88,
pay_amount: 88,
fee_rate: 0,
})
resolveOrderPublicByResumeToken.mockResolvedValue({
data: {
...orderFactory('PAID'),
amount: 100,
pay_amount: 103,
fee_rate: 3,
},
})
const wrapper = mount(PaymentResultView, {
global: {
stubs: {
OrderStatusBadge: true,
},
},
})
await flushPromises()
expect(pollOrderStatus).toHaveBeenCalledWith(42)
expect(resolveOrderPublicByResumeToken).toHaveBeenCalledWith('resume-authoritative')
expect(wrapper.text()).toContain('payment.result.success')
expect(wrapper.text()).toContain('103.00')
expect(wrapper.text()).toContain('100.00')
})
it('refreshes a pending resume-token result until the order becomes paid', async () => {
vi.useFakeTimers()
routeState.query = {