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! bolt11: String!
expiresAt: Date! expiresAt: Date!
cancelled: Boolean! cancelled: Boolean!
cancelledAt: Date
confirmedAt: Date confirmedAt: Date
satsReceived: Int satsReceived: Int
satsRequested: Int! satsRequested: Int!

View File

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

View File

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

View File

@ -11,6 +11,7 @@ export const INVOICE_FIELDS = gql`
satsRequested satsRequested
satsReceived satsReceived
cancelled cancelled
cancelledAt
confirmedAt confirmedAt
expiresAt expiresAt
nostr 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? confirmedAt DateTime?
confirmedIndex BigInt? confirmedIndex BigInt?
cancelled Boolean @default(false) cancelled Boolean @default(false)
cancelledAt DateTime?
msatsRequested BigInt msatsRequested BigInt
msatsReceived BigInt? msatsReceived BigInt?
desc String? 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 }) await paidActions[dbInvoice.actionType].onFail?.({ invoice: dbInvoice }, { models, tx, lnd })
return { return {
cancelled: true cancelled: true,
cancelledAt: new Date()
} }
}, },
...args ...args

View File

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