Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lnbits-iso-dates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostream": patch
---

fix(payments): parse the ISO 8601 `time`/`expiry` returned by LNbits >= 1.0 so paid admissions are confirmed and expired invoices are retired
14 changes: 9 additions & 5 deletions src/payments-processors/lnbits-payment-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { Settings } from '../@types/settings'

const logger = createLogger('lnbits-payments-processor')

// LNbits < 1.0 returns unix seconds for details.time/details.expiry,
// LNbits >= 1.0 returns ISO 8601 strings ("2025-10-11T15:24:27+00:00").
const toDate = (value: number | string): Date => (typeof value === 'number' ? new Date(value * 1000) : new Date(value))

export class LNbitsInvoice implements Invoice {
id: string
pubkey: Pubkey
Expand Down Expand Up @@ -62,7 +66,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor {
invoice.amountPaid = BigInt(Math.floor(data.details.amount / 1000))
}
invoice.unit = InvoiceUnit.SATS
invoice.expiresAt = new Date(data.details.expiry * 1000)
invoice.expiresAt = toDate(data.details.expiry)
if (data.paid) {
invoice.status = InvoiceStatus.COMPLETED
} else if (isExpiredInvoice(invoice)) {
Expand All @@ -71,8 +75,8 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor {
invoice.status = InvoiceStatus.PENDING
}
invoice.description = data.details.memo
invoice.confirmedAt = data.paid ? new Date(data.details.time * 1000) : null
invoice.createdAt = new Date(data.details.time * 1000)
invoice.confirmedAt = data.paid ? toDate(data.details.time) : null
invoice.createdAt = toDate(data.details.time)
invoice.updatedAt = new Date()
return invoice
} catch (error) {
Expand Down Expand Up @@ -129,8 +133,8 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor {
invoice.status = data.paid ? InvoiceStatus.COMPLETED : InvoiceStatus.PENDING
invoice.description = data.details.memo
invoice.confirmedAt = null
invoice.expiresAt = new Date(data.details.expiry * 1000)
invoice.createdAt = new Date(data.details.time * 1000)
invoice.expiresAt = toDate(data.details.expiry)
invoice.createdAt = toDate(data.details.time)
invoice.rawResponse = JSON.stringify({
invoiceResponse: invoiceResponse.data,
createData: response.data,
Expand Down
38 changes: 38 additions & 0 deletions test/unit/payments-processors/lnbits-payment-processor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,43 @@ describe('LNbitsPaymentsProcessor', () => {
expect(invoice.status).to.equal(InvoiceStatus.COMPLETED)
expect(invoice.amountPaid).to.equal(42n)
})

it('parses ISO 8601 time and expiry returned by LNbits >= 1.0', async () => {
const time = new Date(Date.now() - 120000)
const expiry = new Date(Date.now() - 60000)
const { processor } = makeProcessor(
invoiceResponse({
details: {
time: time.toISOString(),
expiry: expiry.toISOString(),
},
}),
)

const invoice = await processor.getInvoice('lnbits-payment-hash')

expect(invoice.status).to.equal(InvoiceStatus.EXPIRED)
expect(invoice.expiresAt?.getTime()).to.equal(expiry.getTime())
expect(invoice.createdAt?.getTime()).to.equal(time.getTime())
})

it('sets a valid confirmedAt for paid invoices with an ISO 8601 time', async () => {
const { processor } = makeProcessor(
invoiceResponse({
data: {
paid: true,
},
details: {
time: '2025-10-11T15:24:27+00:00',
expiry: '2025-10-12T15:24:27+00:00',
},
}),
)

const invoice = await processor.getInvoice('lnbits-payment-hash')

expect(invoice.status).to.equal(InvoiceStatus.COMPLETED)
expect(invoice.confirmedAt?.toISOString()).to.equal('2025-10-11T15:24:27.000Z')
})
})
})
Loading