From 70be81e8c7d5ecab4d490a714fd5e5f906f339f7 Mon Sep 17 00:00:00 2001 From: pory-gone <83127821+pory-gone@users.noreply.github.com> Date: Sat, 10 Jan 2026 18:47:00 +0100 Subject: [PATCH 1/2] check and handling of reciver wallet errors --- api/payIn/transitions.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/api/payIn/transitions.js b/api/payIn/transitions.js index 2fe0ff4bf..9f2a44dfc 100644 --- a/api/payIn/transitions.js +++ b/api/payIn/transitions.js @@ -13,6 +13,13 @@ import { PayInFailureReasonError } from './errors' export const PAY_IN_TERMINAL_STATES = ['PAID', 'FAILED'] export const PAY_IN_PENDING_STATES = Object.values(PayInState).filter(state => !PAY_IN_TERMINAL_STATES.includes(state)) +export const RECEIVER_INVOICE_ERRORS = [ + 'INVOICE_CREATION_FAILED', + 'INVOICE_WRAPPING_FAILED_HIGH_PREDICTED_FEE', + 'INVOICE_WRAPPING_FAILED_HIGH_PREDICTED_EXPIRY', + 'INVOICE_WRAPPING_FAILED_UNKNOWN' +] + const FINALIZE_OPTIONS = { retryLimit: 2 ** 31 - 1, retryBackoff: false, retryDelay: 5, priority: 1000 } async function transitionPayIn (jobName, data, @@ -563,7 +570,7 @@ export async function payInCancel ({ data, models, lnd, boss, ...args }) { export async function payInFailed ({ data, models, lnd, boss, ...args }) { const { payInId, payInFailureReason } = data - return await transitionPayIn('payInFailed', data, { + const transitionedPayIn = await transitionPayIn('payInFailed', data, { payInId, // any of these states can transition to FAILED fromStates: ['PENDING', 'PENDING_HELD', 'HELD', 'FAILED_FORWARD', 'CANCELLED', 'PENDING_INVOICE_CREATION', 'PENDING_INVOICE_WRAP'], @@ -589,6 +596,26 @@ export async function payInFailed ({ data, models, lnd, boss, ...args }) { } } }, { models, lnd, boss, ...args }) + + if (transitionedPayIn?.payOutBolt11 && RECEIVER_INVOICE_ERRORS.includes(transitionedPayIn.payInFailureReason)) { + const { userId: receiverId } = transitionedPayIn.payOutBolt11 + const receiver = await models.user.findUnique({ where: { id: receiverId }, select: { name: true } }) + const senderProtocol = await models.walletProtocol.findFirst({ + where: { + userId: transitionedPayIn.userId, + walletType: 'send', + enabled: true + }, + orderBy: { priority: 'desc' } + }) + if (senderProtocol) { + const logger = walletLogger({ protocolId: senderProtocol.id, userId: transitionedPayIn.userId, payInId, models }) + const reasonText = transitionedPayIn.payInFailureReason.replace(/_/g, ' ').toLowerCase() + const receiverName = receiver?.name || `id:${receiverId}` + logger.error(`payment failed: receiver @${receiverName}'s wallet ${reasonText}`) + } + } + return transitionedPayIn } function deducePayInFailureReason ({ payInFailureReason, payIn, lndPayInBolt11 }) { From 6585b6da60d39f035dbb9725c5d0c2143cc5940d Mon Sep 17 00:00:00 2001 From: pory-gone <83127821+pory-gone@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:07:28 +0100 Subject: [PATCH 2/2] use startsWith() for more flexible error matching --- api/payIn/transitions.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/api/payIn/transitions.js b/api/payIn/transitions.js index 9f2a44dfc..a68d094d3 100644 --- a/api/payIn/transitions.js +++ b/api/payIn/transitions.js @@ -13,13 +13,6 @@ import { PayInFailureReasonError } from './errors' export const PAY_IN_TERMINAL_STATES = ['PAID', 'FAILED'] export const PAY_IN_PENDING_STATES = Object.values(PayInState).filter(state => !PAY_IN_TERMINAL_STATES.includes(state)) -export const RECEIVER_INVOICE_ERRORS = [ - 'INVOICE_CREATION_FAILED', - 'INVOICE_WRAPPING_FAILED_HIGH_PREDICTED_FEE', - 'INVOICE_WRAPPING_FAILED_HIGH_PREDICTED_EXPIRY', - 'INVOICE_WRAPPING_FAILED_UNKNOWN' -] - const FINALIZE_OPTIONS = { retryLimit: 2 ** 31 - 1, retryBackoff: false, retryDelay: 5, priority: 1000 } async function transitionPayIn (jobName, data, @@ -597,7 +590,7 @@ export async function payInFailed ({ data, models, lnd, boss, ...args }) { } }, { models, lnd, boss, ...args }) - if (transitionedPayIn?.payOutBolt11 && RECEIVER_INVOICE_ERRORS.includes(transitionedPayIn.payInFailureReason)) { + if (transitionedPayIn?.payOutBolt11 && (transitionedPayIn.payInFailureReason?.startsWith('INVOICE_CREATION') || transitionedPayIn.payInFailureReason?.startsWith('INVOICE_WRAPPING'))) { const { userId: receiverId } = transitionedPayIn.payOutBolt11 const receiver = await models.user.findUnique({ where: { id: receiverId }, select: { name: true } }) const senderProtocol = await models.walletProtocol.findFirst({