Use invoice.cancelledAt to determine if invoice expired or was canceled by user (#1631)

* Set and resolve invoice.cancelledAt

* Don't close modal if invoice expired
This commit is contained in:
ekzyis 2024-11-22 15:25:20 +01:00 committed by GitHub
parent 96e5c6c51c
commit 923ddb74ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 32 additions and 19 deletions

View File

@ -108,6 +108,7 @@ const typeDefs = `
bolt11: String!
expiresAt: Date!
cancelled: Boolean!
cancelledAt: Date
confirmedAt: Date
satsReceived: Int
satsRequested: Int!

View File

@ -43,7 +43,7 @@ export function CompactLongCountdown (props) {
? ` ${props.formatted.hours}:${props.formatted.minutes}:${props.formatted.seconds}`
: Number(props.formatted.minutes) > 0
? ` ${props.formatted.minutes}:${props.formatted.seconds}`
: Number(props.formatted.seconds) > 0
: Number(props.formatted.seconds) >= 0
? ` ${props.formatted.seconds}s`
: ' '}
</>

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useEffect } from 'react'
import { numWithUnits } from '@/lib/format'
import AccordianItem from './accordian-item'
import Qr, { QrSkeleton } from './qr'
@ -18,10 +18,9 @@ import { Badge } from 'react-bootstrap'
import styles from './invoice.module.css'
export default function Invoice ({
id, query = INVOICE, modal, onPayment, onCanceled, info, successVerb = 'deposited',
id, query = INVOICE, modal, onPayment, onExpired, onCanceled, info, successVerb = 'deposited',
heldVerb = 'settling', useWallet = true, walletError, poll, waitFor, ...props
}) {
const [expired, setExpired] = useState(false)
const { data, error } = useQuery(query, SSR
? {}
: {
@ -33,6 +32,8 @@ export default function Invoice ({
const invoice = data?.invoice
const expired = invoice?.cancelledAt && new Date(invoice.expiresAt) < new Date(invoice.cancelledAt)
useEffect(() => {
if (!invoice) {
return
@ -40,11 +41,12 @@ export default function Invoice ({
if (waitFor?.(invoice)) {
onPayment?.(invoice)
}
if (invoice.cancelled || invoice.actionError) {
if (expired) {
onExpired?.(invoice)
} else if (invoice.cancelled || invoice.actionError) {
onCanceled?.(invoice)
}
setExpired(new Date(invoice.expiredAt) <= new Date())
}, [invoice, onPayment, setExpired])
}, [invoice, expired, onExpired, onCanceled, onPayment])
if (error) {
return <div>{error.message}</div>
@ -78,6 +80,10 @@ export default function Invoice ({
</>
)
useWallet = false
} else if (expired) {
variant = 'failed'
status = 'expired'
useWallet = false
} else if (invoice.cancelled) {
variant = 'failed'
status = 'cancelled'
@ -90,18 +96,10 @@ export default function Invoice ({
</div>
)
useWallet = false
} else if (expired) {
variant = 'failed'
status = 'expired'
useWallet = false
} else {
variant = 'pending'
status = (
<CompactLongCountdown
date={invoice.expiresAt} onComplete={() => {
setExpired(true)
}}
/>
<CompactLongCountdown date={invoice.expiresAt} />
)
}

View File

@ -46,7 +46,12 @@ export const useInvoice = () => {
throw error
}
const { hash, cancelled, actionError, actionState } = data.invoice
const { hash, cancelled, cancelledAt, actionError, actionState, expiresAt } = data.invoice
const expired = cancelledAt && new Date(expiresAt) < new Date(cancelledAt)
if (expired) {
throw new InvoiceExpiredError(hash)
}
if (cancelled || actionError) {
throw new InvoiceCanceledError(hash, actionError)
@ -170,6 +175,7 @@ export const useQrPayment = () => {
useWallet={false}
walletError={walletError}
waitFor={waitFor}
onExpired={inv => reject(new InvoiceExpiredError(inv?.hash))}
onCanceled={inv => { onClose(); reject(new InvoiceCanceledError(inv?.hash, inv?.actionError)) }}
onPayment={() => { paid = true; onClose(); resolve() }}
poll

View File

@ -11,6 +11,7 @@ export const INVOICE_FIELDS = gql`
satsRequested
satsReceived
cancelled
cancelledAt
confirmedAt
expiresAt
nostr

View File

@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Invoice" ADD COLUMN "cancelledAt" TIMESTAMP(3);
UPDATE "Invoice" SET "cancelledAt" = "expiresAt" WHERE "cancelled" = true;

View File

@ -909,6 +909,7 @@ model Invoice {
confirmedAt DateTime?
confirmedIndex BigInt?
cancelled Boolean @default(false)
cancelledAt DateTime?
msatsRequested BigInt
msatsReceived BigInt?
desc String?

View File

@ -456,7 +456,8 @@ export async function paidActionFailed ({ data: { invoiceId, ...args }, models,
await paidActions[dbInvoice.actionType].onFail?.({ invoice: dbInvoice }, { models, tx, lnd })
return {
cancelled: true
cancelled: true,
cancelledAt: new Date()
}
},
...args

View File

@ -182,7 +182,8 @@ export async function checkInvoice ({ data: { hash, invoice }, boss, models, lnd
hash: inv.id
},
data: {
cancelled: true
cancelled: true,
cancelledAt: new Date()
}
}), { models }
)