Fix old invoice passed to QR code
This commit is contained in:
parent
7742257470
commit
7036804c67
@ -54,14 +54,14 @@ export const useInvoice = () => {
|
|||||||
}, [cancelInvoice])
|
}, [cancelInvoice])
|
||||||
|
|
||||||
const retry = useCallback(async ({ id, hash, hmac }) => {
|
const retry = useCallback(async ({ id, hash, hmac }) => {
|
||||||
// always cancel the previous invoice to make sure we can retry and it cannot be paid later
|
|
||||||
await cancel({ hash, hmac })
|
|
||||||
|
|
||||||
console.log('retrying invoice:', hash)
|
console.log('retrying invoice:', hash)
|
||||||
const { data, error } = await retryPaidAction({ variables: { invoiceId: Number(id) } })
|
const { data, error } = await retryPaidAction({ variables: { invoiceId: Number(id) } })
|
||||||
if (error) throw error
|
if (error) throw error
|
||||||
|
|
||||||
return data?.retryPaidAction?.invoice
|
const newInvoice = data.retryPaidAction.invoice
|
||||||
|
console.log('new invoice:', newInvoice?.hash)
|
||||||
|
|
||||||
|
return newInvoice
|
||||||
})
|
})
|
||||||
|
|
||||||
return { cancel, retry, isInvoice }
|
return { cancel, retry, isInvoice }
|
||||||
|
@ -33,26 +33,28 @@ export function usePaidMutation (mutation,
|
|||||||
|
|
||||||
const waitForPayment = useCallback(async (invoice, { alwaysShowQROnFailure = false, persistOnNavigate = false, waitFor }) => {
|
const waitForPayment = useCallback(async (invoice, { alwaysShowQROnFailure = false, persistOnNavigate = false, waitFor }) => {
|
||||||
let walletError
|
let walletError
|
||||||
|
let walletInvoice = invoice
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await waitForWalletPayment(invoice, waitFor)
|
return await waitForWalletPayment(walletInvoice, waitFor)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof WalletAggregateError || err instanceof NoWalletAvailableError) {
|
if (err instanceof WalletAggregateError || err instanceof NoWalletAvailableError) {
|
||||||
walletError = err
|
walletError = err
|
||||||
|
// wallet payment error handling always creates a new invoice to retry
|
||||||
|
if (err.newInvoice) walletInvoice = err.newInvoice
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// bail if the payment took too long to prevent showing a QR code on an unrelated page
|
||||||
(!alwaysShowQROnFailure && Date.now() - start > 1000) ||
|
// (if alwaysShowQROnFailure is not set) or user canceled the invoice or it expired
|
||||||
err instanceof InvoiceCanceledError ||
|
const tooSlow = Date.now() - start > 1000
|
||||||
err instanceof InvoiceExpiredError) {
|
const skipQr = (tooSlow && !alwaysShowQROnFailure) || err instanceof InvoiceCanceledError || err instanceof InvoiceExpiredError
|
||||||
// bail since qr code payment will also fail
|
if (skipQr) {
|
||||||
// also bail if the payment took more than 1 second
|
|
||||||
// and cancel the invoice if it's not already canceled so it can be retried
|
|
||||||
invoiceHelper.cancel(invoice).catch(console.error)
|
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await waitForQrPayment(invoice, walletError, { persistOnNavigate, waitFor })
|
|
||||||
|
return await waitForQrPayment(walletInvoice, walletError, { persistOnNavigate, waitFor })
|
||||||
}, [waitForWalletPayment, waitForQrPayment, invoiceHelper])
|
}, [waitForWalletPayment, waitForQrPayment, invoiceHelper])
|
||||||
|
|
||||||
const innerMutate = useCallback(async ({
|
const innerMutate = useCallback(async ({
|
||||||
|
@ -22,16 +22,18 @@ export class WalletNotEnabledError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SenderError extends Error {
|
export class SenderError extends Error {
|
||||||
constructor (name, hash) {
|
constructor (name, invoice, message) {
|
||||||
super(`${name} failed to pay invoice: ${hash}`)
|
super(`${name} failed to pay invoice ${invoice.hash}: ${message}`)
|
||||||
this.name = 'SenderError'
|
this.name = 'SenderError'
|
||||||
|
this.invoice = invoice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WalletAggregateError extends AggregateError {
|
export class WalletAggregateError extends AggregateError {
|
||||||
constructor (errors) {
|
constructor (errors, newInvoice) {
|
||||||
super(errors)
|
super(errors)
|
||||||
this.name = 'WalletAggregateError'
|
this.name = 'WalletAggregateError'
|
||||||
|
this.newInvoice = newInvoice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { decode as bolt11Decode } from 'bolt11'
|
|
||||||
import { useWallets } from '@/wallets'
|
import { useWallets } from '@/wallets'
|
||||||
import walletDefs from '@/wallets/client'
|
import walletDefs from '@/wallets/client'
|
||||||
import { formatSats } from '@/lib/format'
|
import { formatSats } from '@/lib/format'
|
||||||
@ -44,25 +43,30 @@ export function useWalletPayment () {
|
|||||||
let walletError = new WalletAggregateError([])
|
let walletError = new WalletAggregateError([])
|
||||||
let walletInvoice = invoice
|
let walletInvoice = invoice
|
||||||
|
|
||||||
for (const wallet of walletsWithPayments) {
|
for (const [index, wallet] of walletsWithPayments.entries()) {
|
||||||
const controller = invoiceController(walletInvoice.id, invoiceHelper.isInvoice)
|
const controller = invoiceController(walletInvoice.id, invoiceHelper.isInvoice)
|
||||||
try {
|
try {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
// can't await wallet payments since we might pay hold invoices and thus payments might not settle immediately.
|
// can't await wallet payments since we might pay hold invoices and thus payments might not settle immediately.
|
||||||
// that's why we separately check if we received the payment with the invoice controller.
|
// that's why we separately check if we received the payment with the invoice controller.
|
||||||
wallet.sendPayment(walletInvoice.bolt11).catch(reject)
|
wallet.sendPayment(walletInvoice).catch(reject)
|
||||||
controller.wait(waitFor)
|
controller.wait(waitFor)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject)
|
.catch(reject)
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// create a new invoice which cancels the previous one
|
// cancel invoice to make sure it cannot be paid later.
|
||||||
// to make sure the old one cannot be paid later and we can retry.
|
// we only need to do this if payment was attempted which is not the case if the wallet is not enabled.
|
||||||
// we don't need to do this if payment failed because wallet is not enabled
|
const paymentAttempt = !(err instanceof WalletNotEnabledError)
|
||||||
// because we know that it didn't and won't try to pay.
|
if (paymentAttempt) {
|
||||||
if (!(err instanceof WalletNotEnabledError)) {
|
await invoiceHelper.cancel(walletInvoice)
|
||||||
|
|
||||||
|
// only create new invoice via retry if there is another wallet to try
|
||||||
|
const lastWallet = index === walletsWithPayments.length - 1
|
||||||
|
if (!lastWallet) {
|
||||||
walletInvoice = await invoiceHelper.retry(walletInvoice)
|
walletInvoice = await invoiceHelper.retry(walletInvoice)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: receiver fallbacks
|
// TODO: receiver fallbacks
|
||||||
//
|
//
|
||||||
@ -72,7 +76,7 @@ export function useWalletPayment () {
|
|||||||
// try next wallet if the payment failed because of the wallet
|
// try next wallet if the payment failed because of the wallet
|
||||||
// and not because it expired or was canceled
|
// and not because it expired or was canceled
|
||||||
if (err instanceof WalletNotEnabledError || err instanceof SenderError) {
|
if (err instanceof WalletNotEnabledError || err instanceof SenderError) {
|
||||||
walletError = new WalletAggregateError([...walletError.errors, err])
|
walletError = new WalletAggregateError([...walletError.errors, err], walletInvoice)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +97,7 @@ export function useWalletPayment () {
|
|||||||
|
|
||||||
// ignore errors from disabled wallets, only return payment errors
|
// ignore errors from disabled wallets, only return payment errors
|
||||||
const paymentErrors = walletError.errors.filter(e => !(e instanceof WalletNotEnabledError))
|
const paymentErrors = walletError.errors.filter(e => !(e instanceof WalletNotEnabledError))
|
||||||
throw new WalletAggregateError(paymentErrors)
|
throw new WalletAggregateError(paymentErrors, walletInvoice)
|
||||||
}, [walletsWithPayments, invoiceHelper])
|
}, [walletsWithPayments, invoiceHelper])
|
||||||
|
|
||||||
return waitForPayment
|
return waitForPayment
|
||||||
@ -137,20 +141,21 @@ const invoiceController = (id, isInvoice) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendPayment (wallet, logger) {
|
function sendPayment (wallet, logger) {
|
||||||
return async (bolt11) => {
|
return async (invoice) => {
|
||||||
if (!wallet.config.enabled) {
|
if (!wallet.config.enabled) {
|
||||||
throw new WalletNotEnabledError(wallet.def.name)
|
throw new WalletNotEnabledError(wallet.def.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoded = bolt11Decode(bolt11)
|
const { bolt11, satsRequested } = invoice
|
||||||
logger.info(`↗ sending payment: ${formatSats(decoded.satoshis)}`, { bolt11 })
|
|
||||||
|
logger.info(`↗ sending payment: ${formatSats(satsRequested)}`, { bolt11 })
|
||||||
try {
|
try {
|
||||||
const preimage = await wallet.def.sendPayment(bolt11, wallet.config, { logger })
|
const preimage = await wallet.def.sendPayment(bolt11, wallet.config, { logger })
|
||||||
logger.ok(`↗ payment sent: ${formatSats(decoded.satoshis)}`, { bolt11, preimage })
|
logger.ok(`↗ payment sent: ${formatSats(satsRequested)}`, { bolt11, preimage })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err.message || err.toString?.()
|
const message = err.message || err.toString?.()
|
||||||
logger.error(`payment failed: ${message}`, { bolt11 })
|
logger.error(`payment failed: ${message}`, { bolt11 })
|
||||||
throw new SenderError(wallet.def.name, decoded.tagsObject.payment_hash, message)
|
throw new SenderError(wallet.def.name, invoice, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user