Skip to content

Commit

Permalink
refactor(core): introduce 'ProcessPendingInvoiceResult' type
Browse files Browse the repository at this point in the history
This is to introduce the new type and use it to decide when to mark
invoices as 'proceesing completed' and 'paid'. The existing logic is
kept in place an no iteration has been done on that as yet here. That
will be addressed in follow-up commits.
  • Loading branch information
vindard committed Feb 19, 2024
1 parent ada1c23 commit b4dff9a
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 126 deletions.
112 changes: 69 additions & 43 deletions core/api/src/app/wallets/decline-single-pending-invoice.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
ProcessPendingInvoiceResult,
ProcessedReason,
} from "./process-pending-invoice-result"

import { InvoiceNotFoundError } from "@/domain/bitcoin/lightning"

import { InvalidNonHodlInvoiceError } from "@/domain/errors"
Expand All @@ -18,66 +23,87 @@ export const declineHeldInvoice = wrapAsyncToRunInSpan({
walletInvoice: WalletInvoiceWithOptionalLnInvoice
logger: Logger
}): Promise<boolean | ApplicationError> => {
const { pubkey, paymentHash } = walletInvoice
addAttributesToCurrentSpan({ paymentHash, pubkey })

const lndService = LndService()
if (lndService instanceof Error) return lndService

const walletInvoicesRepo = WalletInvoicesRepository()

const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash })

const { paymentHash, pubkey } = walletInvoice
const pendingInvoiceLogger = logger.child({
hash: paymentHash,
pubkey,
lnInvoiceLookup,
topic: "payment",
protocol: "lightning",
transactionType: "receipt",
onUs: false,
})

if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
const result = await processPendingInvoiceForDecline({
walletInvoice,
logger: pendingInvoiceLogger,
})

if (result.isProcessed) {
const processingCompletedInvoice =
await walletInvoicesRepo.markAsProcessingCompleted(paymentHash)
await WalletInvoicesRepository().markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return processingCompletedInvoice
}
return false
}
if (lnInvoiceLookup instanceof Error) return lnInvoiceLookup

if (lnInvoiceLookup.isSettled) {
return new InvalidNonHodlInvoiceError(
JSON.stringify({ paymentHash: lnInvoiceLookup.paymentHash }),
)
}
const error = "error" in result && result.error
return !(result.isPaid || result.isProcessed)
? false
: result.isProcessed
? false
: error
? error
: result.isPaid
},
})

if (!lnInvoiceLookup.isHeld) {
pendingInvoiceLogger.info({ lnInvoiceLookup }, "invoice has not been paid yet")
return false
}
export const processPendingInvoiceForDecline = async ({
walletInvoice,
logger: pendingInvoiceLogger,
}: {
walletInvoice: WalletInvoiceWithOptionalLnInvoice
logger: Logger
}): Promise<ProcessPendingInvoiceResult> => {
const { pubkey, paymentHash } = walletInvoice
addAttributesToCurrentSpan({ paymentHash, pubkey })

let heldForMsg = ""
if (lnInvoiceLookup.heldAt) {
heldForMsg = `for ${elapsedSinceTimestamp(lnInvoiceLookup.heldAt)}s `
}
pendingInvoiceLogger.error(
{ lnInvoiceLookup },
`invoice has been held ${heldForMsg}and is now been cancelled`,
)
// Fetch invoice from lnd service
const lndService = LndService()
if (lndService instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(lndService)
}

const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) return invoiceSettled
const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash })
if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
return ProcessPendingInvoiceResult.processedOnly(ProcessedReason.InvoiceNotFound)
}
if (lnInvoiceLookup instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(lnInvoiceLookup)
}

const processingCompletedInvoice =
await walletInvoicesRepo.markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
}
// Check status on invoice fetched from lnd
const { isSettled, isHeld } = lnInvoiceLookup
if (isSettled) {
return ProcessPendingInvoiceResult.paidWithError(
new InvalidNonHodlInvoiceError(JSON.stringify({ paymentHash })),
)
}
if (!isHeld) {
pendingInvoiceLogger.info({ lnInvoiceLookup }, "invoice has not been paid yet")
ProcessPendingInvoiceResult.notPaid()
}

return true
},
})
// Cancel held invoice
const { heldAt } = lnInvoiceLookup
const heldForMsg = heldAt ? `for ${elapsedSinceTimestamp(heldAt)}s ` : ""
pendingInvoiceLogger.error(
{ lnInvoiceLookup },
`invoice has been held ${heldForMsg}and is now been cancelled`,
)
const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(invoiceSettled)
}

return ProcessPendingInvoiceResult.ok()
}
24 changes: 24 additions & 0 deletions core/api/src/app/wallets/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,27 @@ type LnurlPaymentSendArgs = {
lnurl: string
amount: number
}

type ProcessedReason =
(typeof import("./process-pending-invoice-result").ProcessedReason)[keyof typeof import("./process-pending-invoice-result").ProcessedReason]

type ProcessPendingInvoiceResult =
| {
isProcessed: true
isPaid: true
}
| {
isProcessed: false
isPaid: true
error?: ApplicationError
}
| {
isProcessed: true
isPaid: false
reason: ProcessedReason
}
| {
isProcessed: false
isPaid: false
error?: ApplicationError
}
34 changes: 34 additions & 0 deletions core/api/src/app/wallets/process-pending-invoice-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const ProcessedReason = {
InvoiceNotFound: "InvoiceNotFound",
InvoiceCanceled: "InvoiceCanceled",
} as const

export const ProcessPendingInvoiceResult = {
ok: (): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: true,
}),
paidOnly: (): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: true,
}),
paidWithError: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: true,
error,
}),
processedOnly: (reason: ProcessedReason): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: false,
reason,
}),
notPaid: (): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
}),
err: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
error,
}),
}
Loading

0 comments on commit b4dff9a

Please sign in to comment.