Recover failed Lightning file attempts without blocking other payment methods

This commit is contained in:
archipelago
2026-10-06 19:59:47 -04:00
parent 0dda84e5c4
commit d94ff097d2
6 changed files with 322 additions and 61 deletions
@@ -637,4 +637,20 @@ describe('RPCClient convenience methods', () => {
await rpcClient.diskCleanup()
expect(getLastMethod()).toBe('system.disk-cleanup')
})
it.each([
[{ status: 'failed', payment_hash: 'a'.repeat(64), failure_reason: 'No route' }, 'failed'],
[{ status: 'unknown', payment_hash: 'a'.repeat(64) }, 'pending'],
[{ status: 'new-status', payment_hash: 'a'.repeat(64) }, 'pending'],
[{ status: '', payment_hash: 'a'.repeat(64) }, 'pending'],
[{}, 'pending'],
[{ status: 'succeeded', payment_hash: 'a'.repeat(64) }, 'succeeded'],
[{ payment_hash: 'a'.repeat(64), amount_sats: 5 }, 'succeeded'],
])('keeps terminal, uncertain and legacy payment states distinct: %j', async (response, expected) => {
mockFetch.mockResolvedValueOnce(jsonResponse({ result: response }))
const result = await rpcClient.payLightningInvoice({ payment_request: 'ln-test' })
expect(result.status).toBe(expected)
if (expected === 'failed') expect(result.failure_reason).toBe('No route')
expect(mockFetch).toHaveBeenCalledOnce()
})
})
+11 -2
View File
@@ -490,19 +490,28 @@ class RPCClient {
status?: string
payment_hash?: string
amount_sats?: number
failure_reason?: string
}>({
method: 'lnd.payinvoice',
params,
// Above the backend's 120s wait so the backend always answers first.
timeout: 130000,
maxRetries: 1,
})
const hash = res.payment_hash || ''
const amount = res.amount_sats || 0
// Older backends have no status field — a plain response was a success.
if (res.status !== 'pending') {
if (res.status === 'failed') {
return { status: 'failed', payment_hash: hash, amount_sats: amount, failure_reason: res.failure_reason || 'Payment failed' }
}
// Legacy success must include the canonical payment hash. An unknown,
// malformed or newly introduced status must never become invented success.
if (res.status === 'succeeded' || (res.status === undefined && /^[a-f0-9]{64}$/i.test(hash))) {
return { status: 'succeeded', payment_hash: hash, amount_sats: amount }
}
if (res.status !== 'pending' && res.status !== 'in_flight') {
return { status: 'pending', payment_hash: hash, amount_sats: amount }
}
if (!hash) return { status: 'pending', payment_hash: '', amount_sats: amount }
// Let the caller unblock its UI right now ("settling…") — the backend