Skip to content

Commit

Permalink
refactor(core): introduce 'ProcessPendingInvoiceResult' type to pendi…
Browse files Browse the repository at this point in the history
…ng invoice handling
  • Loading branch information
vindard committed Feb 16, 2024
1 parent ada1c23 commit de45faa
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 120 deletions.
105 changes: 63 additions & 42 deletions core/api/src/app/wallets/decline-single-pending-invoice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { InvoiceNotFoundError } from "@/domain/bitcoin/lightning"
import { ProcessPendingInvoiceResult } from "./process-pending-invoice-result"

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

import { InvalidNonHodlInvoiceError } from "@/domain/errors"

Expand All @@ -18,66 +23,82 @@ 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 processPendingInvoice({
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 }),
)
}
return true
},
})

if (!lnInvoiceLookup.isHeld) {
pendingInvoiceLogger.info({ lnInvoiceLookup }, "invoice has not been paid yet")
return false
}
const processPendingInvoice = 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`,
const lndService = LndService()
if (lndService instanceof Error) return ProcessPendingInvoiceResult.paidOnly(lndService)

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

if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
return ProcessPendingInvoiceResult.err(lnInvoiceLookup)
}
if (lnInvoiceLookup instanceof Error) {
return ProcessPendingInvoiceResult.paidOnly(lnInvoiceLookup)
}

if (lnInvoiceLookup.isSettled) {
return ProcessPendingInvoiceResult.paidOnly(
new InvalidNonHodlInvoiceError(
JSON.stringify({ paymentHash: lnInvoiceLookup.paymentHash }),
),
)
}

const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) return invoiceSettled
if (!lnInvoiceLookup.isHeld) {
pendingInvoiceLogger.info({ lnInvoiceLookup }, "invoice has not been paid yet")
ProcessPendingInvoiceResult.err(new InvoiceNotPaidOnLndError())
}

const processingCompletedInvoice =
await walletInvoicesRepo.markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
}
let heldForMsg = ""
if (lnInvoiceLookup.heldAt) {
heldForMsg = `for ${elapsedSinceTimestamp(lnInvoiceLookup.heldAt)}s `
}
pendingInvoiceLogger.error(
{ lnInvoiceLookup },
`invoice has been held ${heldForMsg}and is now been cancelled`,
)

return true
},
})
const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) {
return ProcessPendingInvoiceResult.paidOnly(invoiceSettled)
}

return ProcessPendingInvoiceResult.ok()
}
7 changes: 7 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,10 @@ type LnurlPaymentSendArgs = {
lnurl: string
amount: number
}

type ProcessPendingInvoiceResult = {
isPaid: boolean
isProcessed: boolean
isDeclined: boolean
error: ApplicationError | undefined
}
32 changes: 32 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,32 @@
export const ProcessPendingInvoiceResult = {
ok: (): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: true,
isDeclined: false,
error: undefined,
}),
paidOnly: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: true,
isDeclined: false,
error,
}),
processedOnly: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: false,
isDeclined: false,
error,
}),
declineOnly: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
isDeclined: true,
error,
}),
err: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
isDeclined: false,
error,
}),
}
Loading

0 comments on commit de45faa

Please sign in to comment.