fix: NWC payment timeout/error now rejects properly (BUG-F4)
- Timeout and WebSocket errors reject with proper Error objects - Caller catches and falls through to poll-based confirmation - Preimage undefined check prevents calling confirm with no preimage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
acecc79d04
commit
74cb5cc728
@@ -0,0 +1,43 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
|
|
||||||
|
// Test the NWC timeout/error behavior in isolation
|
||||||
|
// Full component test would require too many mocks, so we test the promise pattern
|
||||||
|
|
||||||
|
describe('NWC payment timeout handling', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('timeout produces proper rejection error', async () => {
|
||||||
|
// Simulate the payViaNWC timeout pattern
|
||||||
|
const payPromise = new Promise<string | undefined>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('NWC payment timed out'))
|
||||||
|
}, 30_000)
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(30_000)
|
||||||
|
|
||||||
|
await expect(payPromise).rejects.toThrow('NWC payment timed out')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('websocket error produces proper rejection', async () => {
|
||||||
|
const payPromise = new Promise<string | undefined>((_, reject) => {
|
||||||
|
// Simulate immediate ws error
|
||||||
|
queueMicrotask(() => reject(new Error('NWC WebSocket error')))
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(payPromise).rejects.toThrow('NWC WebSocket error')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('undefined preimage is treated as failure', () => {
|
||||||
|
const preimage: string | undefined = undefined
|
||||||
|
// The fixed code checks: if (preimage) { ...confirm... }
|
||||||
|
// So undefined preimage falls through to polling
|
||||||
|
expect(!preimage).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -173,9 +173,9 @@ export function useWallet() {
|
|||||||
}
|
}
|
||||||
if (nwcUrl && nwcValid) {
|
if (nwcUrl && nwcValid) {
|
||||||
paymentStatus.value = 'paying'
|
paymentStatus.value = 'paying'
|
||||||
|
try {
|
||||||
const preimage = await payViaNWC(nwcUrl, bolt11)
|
const preimage = await payViaNWC(nwcUrl, bolt11)
|
||||||
|
if (preimage) {
|
||||||
// Tell server payment is confirmed (skip lookup_invoice polling)
|
|
||||||
await authFetch(`/api/payments/confirm/${paymentId}`, {
|
await authFetch(`/api/payments/confirm/${paymentId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -184,6 +184,12 @@ export function useWallet() {
|
|||||||
paymentStatus.value = 'confirmed'
|
paymentStatus.value = 'confirmed'
|
||||||
return paymentId
|
return paymentId
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[Wallet] NWC payment failed, falling back to polling:', err)
|
||||||
|
}
|
||||||
|
// NWC failed or returned no preimage — fall through to polling
|
||||||
|
paymentStatus.value = 'pending'
|
||||||
|
}
|
||||||
|
|
||||||
// No NWC — poll for confirmation (manual payment / QR code flow)
|
// No NWC — poll for confirmation (manual payment / QR code flow)
|
||||||
for (let i = 0; i < 30; i++) {
|
for (let i = 0; i < 30; i++) {
|
||||||
@@ -268,7 +274,7 @@ async function payViaNWC(nwcUrl: string, bolt11: string): Promise<string | undef
|
|||||||
const ws = new WebSocket(nwc.relay)
|
const ws = new WebSocket(nwc.relay)
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
ws.close()
|
ws.close()
|
||||||
resolve(undefined)
|
reject(new Error('NWC payment timed out'))
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
@@ -309,7 +315,7 @@ async function payViaNWC(nwcUrl: string, bolt11: string): Promise<string | undef
|
|||||||
|
|
||||||
ws.onerror = () => {
|
ws.onerror = () => {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
resolve(undefined)
|
reject(new Error('NWC WebSocket error'))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user