Skip to content

Commit

Permalink
refactor(core): enforce that 'processedPaid' and 'processCanceledOrEx…
Browse files Browse the repository at this point in the history
…pired' are mutually exclusive
  • Loading branch information
vindard committed Feb 19, 2024
1 parent eedb189 commit df31fab
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 54 deletions.
4 changes: 4 additions & 0 deletions core/api/src/app/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import * as KratosErrors from "@/services/kratos/errors"
import * as BriaEventErrors from "@/services/bria/errors"
import * as SvixErrors from "@/services/svix/errors"

import * as WalletErrors from "@/app/wallets/errors"

export const ApplicationErrors = {
...SharedErrors,
...DomainErrors,
Expand Down Expand Up @@ -56,4 +58,6 @@ export const ApplicationErrors = {
...LedgerFacadeErrors,
...BriaEventErrors,
...SvixErrors,

...WalletErrors,
} as const
36 changes: 21 additions & 15 deletions core/api/src/app/wallets/decline-single-pending-invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
ProcessedReason,
} from "./process-pending-invoice-result"

import { InvalidInvoiceProcessingStateError } from "./errors"

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

import { InvalidNonHodlInvoiceError } from "@/domain/errors"
Expand Down Expand Up @@ -38,22 +40,26 @@ export const declineHeldInvoice = wrapAsyncToRunInSpan({
logger: pendingInvoiceLogger,
})

if (result.markProcessedAsCanceledOrExpired()) {
const processingCompletedInvoice =
await WalletInvoicesRepository().markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return processingCompletedInvoice
}
}
const walletInvoices = WalletInvoicesRepository()
let marked: WalletInvoiceWithOptionalLnInvoice | RepositoryError
switch (true) {
case result.markProcessedAsCanceledOrExpired():
marked = await walletInvoices.markAsProcessingCompleted(paymentHash)
if (marked instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return marked
}
return true

case result.reason() === ProcessedReason.InvoiceNotPaidYet:
return true

return !(result.markProcessedAsPaid() || result.markProcessedAsCanceledOrExpired())
? false
: result.markProcessedAsCanceledOrExpired()
? false
: result.error()
? result.error()
: result.markProcessedAsPaid()
case !!result.error():
return result.error() as ApplicationError

default:
return new InvalidInvoiceProcessingStateError(JSON.stringify(result._state()))
}
},
})

Expand Down
6 changes: 6 additions & 0 deletions core/api/src/app/wallets/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { DomainError, ErrorLevel } from "@/domain/shared"

export class InvoiceProcessingError extends DomainError {}
export class InvalidInvoiceProcessingStateError extends InvoiceProcessingError {
level = ErrorLevel.Critical
}
14 changes: 8 additions & 6 deletions core/api/src/app/wallets/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,21 +141,23 @@ type ProcessPendingInvoiceResultState =
}
| {
markProcessedAsPaid: true
error?: ApplicationError
error: ApplicationError
}
| {
markProcessedAsCanceledOrExpired: true
markProcessedAsPaid: false
reason: ProcessedReason
}
| {
markProcessedAsCanceledOrExpired: false
markProcessedAsPaid: false
error?: ApplicationError
reason: "InvoiceNotPaidYet"
}
| {
error: ApplicationError
}

type ProcessPendingInvoiceResult = {
_state: () => ProcessPendingInvoiceResultState
markProcessedAsCanceledOrExpired: () => boolean
markProcessedAsPaid: () => boolean
error: () => boolean | ApplicationError
reason: () => ProcessedReason | false
error: () => false | ApplicationError
}
12 changes: 6 additions & 6 deletions core/api/src/app/wallets/process-pending-invoice-result.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
export const ProcessedReason = {
InvoiceNotFound: "InvoiceNotFound",
InvoiceCanceled: "InvoiceCanceled",
InvoiceNotPaidYet: "InvoiceNotPaidYet",
} as const

const wrapper = (
state: ProcessPendingInvoiceResultState,
): ProcessPendingInvoiceResult => {
return {
_state: () => state,
markProcessedAsCanceledOrExpired: () =>
"markProcessedAsCanceledOrExpired" in state &&
state.markProcessedAsCanceledOrExpired,
markProcessedAsPaid: () => state.markProcessedAsPaid,
markProcessedAsPaid: () =>
"markProcessedAsPaid" in state && state.markProcessedAsPaid,
error: () => ("error" in state && state.error ? state.error : false),
reason: () => ("reason" in state && state.reason ? state.reason : false),
}
}

Expand All @@ -28,18 +32,14 @@ export const ProcessPendingInvoiceResult = {
processAsCanceledOrExpired: (reason: ProcessedReason): ProcessPendingInvoiceResult =>
wrapper({
markProcessedAsCanceledOrExpired: true,
markProcessedAsPaid: false,
reason,
}),
notPaid: (): ProcessPendingInvoiceResult =>
wrapper({
markProcessedAsCanceledOrExpired: false,
markProcessedAsPaid: false,
reason: ProcessedReason.InvoiceNotPaidYet,
}),
err: (error: ApplicationError): ProcessPendingInvoiceResult =>
wrapper({
markProcessedAsCanceledOrExpired: false,
markProcessedAsPaid: false,
error,
}),
}
63 changes: 36 additions & 27 deletions core/api/src/app/wallets/update-single-pending-invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
ProcessedReason,
} from "./process-pending-invoice-result"

import { InvalidInvoiceProcessingStateError } from "./errors"

import { removeDeviceTokens } from "@/app/users/remove-device-tokens"
import { getCurrentPriceAsDisplayPriceRatio, usdFromBtcMidPriceFn } from "@/app/prices"

Expand Down Expand Up @@ -47,8 +49,7 @@ export const updatePendingInvoice = wrapAsyncToRunInSpan({
}: {
walletInvoice: WalletInvoiceWithOptionalLnInvoice
logger: Logger
}): Promise<boolean | ApplicationError> => {
const walletInvoices = WalletInvoicesRepository()
}): Promise<true | ApplicationError> => {
const { paymentHash, recipientWalletDescriptor: recipientInvoiceWalletDescriptor } =
walletInvoice

Expand All @@ -66,32 +67,40 @@ export const updatePendingInvoice = wrapAsyncToRunInSpan({
logger: pendingInvoiceLogger,
})

if (result.markProcessedAsCanceledOrExpired()) {
const processingCompletedInvoice =
await walletInvoices.markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return processingCompletedInvoice
}
}

if (result.markProcessedAsPaid() && !walletInvoice.paid) {
const invoicePaid = await walletInvoices.markAsPaid(walletInvoice.paymentHash)
if (
invoicePaid instanceof Error &&
!(invoicePaid instanceof CouldNotFindWalletInvoiceError)
) {
return invoicePaid
}
const walletInvoices = WalletInvoicesRepository()
let marked: WalletInvoiceWithOptionalLnInvoice | RepositoryError
switch (true) {
// TODO: can this check be moved to above 'processPendingInvoice' call?
case walletInvoice.paid:
return true

case result.markProcessedAsCanceledOrExpired():
marked = await walletInvoices.markAsProcessingCompleted(paymentHash)
if (marked instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return marked
}
return true

case result.markProcessedAsPaid():
marked = await walletInvoices.markAsPaid(walletInvoice.paymentHash)
if (
marked instanceof Error &&
!(marked instanceof CouldNotFindWalletInvoiceError)
) {
return marked
}
return result.error() || true

case result.reason() === ProcessedReason.InvoiceNotPaidYet:
return true

case !!result.error():
return result.error() as ApplicationError

default:
return new InvalidInvoiceProcessingStateError(JSON.stringify(result._state()))
}

return !(result.markProcessedAsPaid() || result.markProcessedAsCanceledOrExpired())
? false
: result.markProcessedAsCanceledOrExpired()
? false
: result.error()
? result.error()
: result.markProcessedAsPaid()
},
})

Expand Down
2 changes: 2 additions & 0 deletions core/api/src/graphql/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,8 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => {
case "InvalidErrorCodeForPhoneMetadataError":
case "InvalidCountryCodeForPhoneMetadataError":
case "MultipleWalletsFoundForAccountIdAndCurrency":
case "InvoiceProcessingError":
case "InvalidInvoiceProcessingStateError":
message = `Unexpected error occurred, please try again or contact support if it persists (code: ${
error.name
}${error.message ? ": " + error.message : ""})`
Expand Down

0 comments on commit df31fab

Please sign in to comment.