fix payment resume result consistency
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user