From 923ddb74ca5b4463cb1dfb7fe9727bc483dd47d4 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 22 Nov 2024 15:25:20 +0100 Subject: [PATCH] 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 --- api/typeDefs/wallet.js | 1 + components/countdown.js | 2 +- components/invoice.js | 28 +++++++++---------- components/payment.js | 8 +++++- fragments/wallet.js | 1 + .../migration.sql | 4 +++ prisma/schema.prisma | 1 + worker/paidAction.js | 3 +- worker/wallet.js | 3 +- 9 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 prisma/migrations/20241122041724_invoice_cancelled_at/migration.sql 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 } )