diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js
index a7b32ad5..20fd7937 100644
--- a/api/typeDefs/wallet.js
+++ b/api/typeDefs/wallet.js
@@ -108,6 +108,7 @@ const typeDefs = `
bolt11: String!
expiresAt: Date!
cancelled: Boolean!
+ cancelledAt: Date
confirmedAt: Date
satsReceived: Int
satsRequested: Int!
diff --git a/components/countdown.js b/components/countdown.js
index ec6a80d3..e1ef02e1 100644
--- a/components/countdown.js
+++ b/components/countdown.js
@@ -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`
: ' '}
>
diff --git a/components/invoice.js b/components/invoice.js
index cdc9bc2f..d74fff45 100644
--- a/components/invoice.js
+++ b/components/invoice.js
@@ -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
{error.message}
@@ -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 ({
)
useWallet = false
- } else if (expired) {
- variant = 'failed'
- status = 'expired'
- useWallet = false
} else {
variant = 'pending'
status = (
- {
- setExpired(true)
- }}
- />
+
)
}
diff --git a/components/payment.js b/components/payment.js
index 175ca2b3..893a6b0b 100644
--- a/components/payment.js
+++ b/components/payment.js
@@ -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
diff --git a/fragments/wallet.js b/fragments/wallet.js
index fe1f543b..c2bfa3b0 100644
--- a/fragments/wallet.js
+++ b/fragments/wallet.js
@@ -11,6 +11,7 @@ export const INVOICE_FIELDS = gql`
satsRequested
satsReceived
cancelled
+ cancelledAt
confirmedAt
expiresAt
nostr
diff --git a/prisma/migrations/20241122041724_invoice_cancelled_at/migration.sql b/prisma/migrations/20241122041724_invoice_cancelled_at/migration.sql
new file mode 100644
index 00000000..e24263d4
--- /dev/null
+++ b/prisma/migrations/20241122041724_invoice_cancelled_at/migration.sql
@@ -0,0 +1,4 @@
+-- AlterTable
+ALTER TABLE "Invoice" ADD COLUMN "cancelledAt" TIMESTAMP(3);
+
+UPDATE "Invoice" SET "cancelledAt" = "expiresAt" WHERE "cancelled" = true;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 83a68c70..5fd08192 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -909,6 +909,7 @@ model Invoice {
confirmedAt DateTime?
confirmedIndex BigInt?
cancelled Boolean @default(false)
+ cancelledAt DateTime?
msatsRequested BigInt
msatsReceived BigInt?
desc String?
diff --git a/worker/paidAction.js b/worker/paidAction.js
index 63ec26fb..ad9dfb54 100644
--- a/worker/paidAction.js
+++ b/worker/paidAction.js
@@ -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
diff --git a/worker/wallet.js b/worker/wallet.js
index 4e15ce8c..454c5ca7 100644
--- a/worker/wallet.js
+++ b/worker/wallet.js
@@ -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 }
)