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:
parent
96e5c6c51c
commit
923ddb74ca
|
@ -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!
|
||||||
|
|
|
@ -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`
|
||||||
: ' '}
|
: ' '}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -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)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const INVOICE_FIELDS = gql`
|
||||||
satsRequested
|
satsRequested
|
||||||
satsReceived
|
satsReceived
|
||||||
cancelled
|
cancelled
|
||||||
|
cancelledAt
|
||||||
confirmedAt
|
confirmedAt
|
||||||
expiresAt
|
expiresAt
|
||||||
nostr
|
nostr
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Invoice" ADD COLUMN "cancelledAt" TIMESTAMP(3);
|
||||||
|
|
||||||
|
UPDATE "Invoice" SET "cancelledAt" = "expiresAt" WHERE "cancelled" = true;
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue