fix(lightning): never report a slow in-flight payment as failed
Slow multi-hop payments (>15s routing) surfaced as "Payment failed" while LND settled them in the background: the shared LND REST client's 15s total timeout aborted the synchronous /v1/channels/transactions wait, and every UI path treated that abort as a definitive failure. The payment then succeeded anyway and only appeared in history on the next background poll. Backend: lnd.payinvoice now decodes the invoice up front for its payment hash, pays on a dedicated 120s client, and answers status:"pending" with the hash (never an error) when the wait elapses after the payment was handed to LND — only a pre-connect failure is still a hard error. New lnd.paymentstatus RPC reports succeeded/failed/in_flight (with humanized failure reasons) from /v1/payments. Frontend: new rpcClient.payLightningInvoice() pays then polls lnd.paymentstatus to a real terminal state (3s interval, up to 2 min); all five call sites (send modal, scan modal, web5 unified send, peer-file purchase, app-launcher payments) migrated. Failure is only shown when LND itself declares FAILED; a still-settling payment shows an in-flight state and success fires the transaction refresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
aa272bfcf4
commit
9cd507269c
@@ -1320,8 +1320,8 @@ async function payWithInvoice() {
|
||||
|
||||
/**
|
||||
* Pay the seller's invoice straight from THIS node's Lightning wallet, then
|
||||
* release the file. No QR/polling: lnd.payinvoice only returns once the payment
|
||||
* settles, so the payment_hash is immediately valid as the download gate token.
|
||||
* release the file. payLightningInvoice resolves to a real terminal state, so
|
||||
* on success the payment_hash is immediately valid as the download gate token.
|
||||
*/
|
||||
async function payWithLightning() {
|
||||
const item = payItem.value
|
||||
@@ -1341,14 +1341,15 @@ async function payWithLightning() {
|
||||
lnError.value = inv?.error || 'The seller could not create an invoice (is its Lightning node running?).'
|
||||
return
|
||||
}
|
||||
// 2. Pay it from our own node. Returns only after settlement.
|
||||
const pay = await rpcClient.call<{ payment_hash?: string; payment_error?: string }>({
|
||||
method: 'lnd.payinvoice',
|
||||
params: { payment_request: inv.bolt11 },
|
||||
timeout: 120000,
|
||||
})
|
||||
if (pay?.payment_error) {
|
||||
lnError.value = `Payment failed: ${pay.payment_error}`
|
||||
// 2. Pay it from our own node. Tracked to a REAL terminal state — a slow
|
||||
// multi-hop route resolves via status polling instead of a false failure.
|
||||
const pay = await rpcClient.payLightningInvoice({ payment_request: inv.bolt11 })
|
||||
if (pay.status === 'failed') {
|
||||
lnError.value = `Payment failed: ${pay.failure_reason || 'unknown reason'}`
|
||||
return
|
||||
}
|
||||
if (pay.status === 'pending') {
|
||||
lnError.value = 'Payment is still settling — this can take a few minutes. Check your wallet transactions before paying again.'
|
||||
return
|
||||
}
|
||||
// 3. Settled — pull the file using the payment hash as the gate token.
|
||||
|
||||
Reference in New Issue
Block a user