diff --git a/src/modules/wallet/services/WalletService.ts b/src/modules/wallet/services/WalletService.ts index cbfcd0b..7607b99 100644 --- a/src/modules/wallet/services/WalletService.ts +++ b/src/modules/wallet/services/WalletService.ts @@ -352,11 +352,37 @@ export default class WalletService extends BaseService { const payments = await response.json() // Transform to our transaction format - this._transactions.value = payments - .map((payment: any) => this.mapPaymentToTransaction(payment)) - .sort((a: PaymentTransaction, b: PaymentTransaction) => - b.timestamp.getTime() - a.timestamp.getTime() - ) + this._transactions.value = payments.map((payment: any) => { + let timestamp = new Date() + + if (payment.time) { + // Check if it's an ISO string or Unix timestamp + if (typeof payment.time === 'string' && payment.time.includes('T')) { + // ISO string format (e.g., "2025-09-14T16:49:40.378877+00:00") + timestamp = new Date(payment.time) + } else if (typeof payment.time === 'number' || !isNaN(Number(payment.time))) { + // Unix timestamp (seconds) - multiply by 1000 for milliseconds + timestamp = new Date(Number(payment.time) * 1000) + } else { + // Try to parse as-is + timestamp = new Date(payment.time) + } + } + + + return { + id: payment.payment_hash, + amount: Math.abs(payment.amount) / 1000, + description: payment.memo || payment.description || 'No description', + timestamp: timestamp, + type: payment.amount > 0 ? 'received' : 'sent', + status: payment.pending ? 'pending' : 'confirmed', + fee: payment.fee ? payment.fee / 1000 : undefined, + tag: payment.tag || (payment.extra && payment.extra.tag) || null + } + }).sort((a: PaymentTransaction, b: PaymentTransaction) => + b.timestamp.getTime() - a.timestamp.getTime() + ) console.log(`Loaded ${payments.length} transactions`) } @@ -452,67 +478,51 @@ export default class WalletService extends BaseService { * Map LNbits payment object to our transaction format */ private mapPaymentToTransaction(payment: any): PaymentTransaction { - // `time` is serialized by pydantic as an ISO-8601 string - // ("2025-09-14T16:49:40.378877+00:00"). Older payloads and some - // extensions still hand back a unix-seconds number, so accept both. + // Handle timestamp parsing - try different formats let timestamp = new Date() if (payment.time) { - if (typeof payment.time === 'number') { - timestamp = new Date(payment.time * 1000) - } else if (typeof payment.time === 'string' && !payment.time.includes('T')) { - // Numeric string = unix seconds - const asNumber = Number(payment.time) - timestamp = isNaN(asNumber) ? new Date(payment.time) : new Date(asNumber * 1000) - } else { + if (typeof payment.time === 'string') { + // ISO string format timestamp = new Date(payment.time) + } else if (typeof payment.time === 'number') { + // Unix timestamp (seconds) + timestamp = new Date(payment.time * 1000) } } - // `amount` is signed millisats: positive incoming, negative outgoing. - const amountMsat = Number(payment.amount ?? 0) - const amountSats = Math.abs(amountMsat) / 1000 + // For the transaction display, convert amount from millisats to sats + const amountSats = Math.abs(payment.amount) / 1000 + + // Map status correctly - be more explicit about the mapping + let status: 'pending' | 'confirmed' | 'failed' = 'pending' + + // Check for pending first + if (payment.pending === true) { + status = 'pending' + } + // Check for success status + else if (payment.status === 'success' || payment.status === 'settled' || payment.status === 'confirmed') { + status = 'confirmed' + } + // Check for failed status + else if (payment.status === 'failed') { + status = 'failed' + } + // If status is success but no pending field, assume confirmed + else if (payment.status === 'success' && payment.pending !== true) { + status = 'confirmed' + } - // `fee` is signed millisats as well. A zero fee is left undefined so the - // UI can omit the "Fee:" line entirely rather than printing "0 sats". - const feeMsat = Math.abs(Number(payment.fee ?? 0)) return { id: payment.payment_hash || payment.checking_id || payment.id, amount: amountSats, - description: payment.memo || payment.description || 'No description', + description: payment.description || payment.memo || 'Payment', timestamp: timestamp, - type: amountMsat > 0 ? 'received' : 'sent', - status: this.mapPaymentStatus(payment), - fee: feeMsat > 0 ? feeMsat / 1000 : undefined, - tag: payment.tag || payment.extra?.tag || null - } - } - - /** - * Map an LNbits payment status onto our three display states. - * - * LNbits serializes `PaymentState` as "pending" | "success" | "failed" - * (lnbits/core/models/payments.py). `Payment.pending` is a Python - * `@property`, and LNbits pins pydantic 1.x, which never serializes - * properties - so `pending` is absent from every REST and WebSocket - * payload. Reading it returned `undefined` for every payment, which is - * falsy, so everything rendered as "confirmed": pending payments looked - * settled and failed payments looked successful. - * - * An unrecognized status is treated as pending rather than confirmed: - * showing a payment as still in flight is the safe direction to be wrong. - */ - private mapPaymentStatus(payment: any): 'pending' | 'confirmed' | 'failed' { - switch (payment.status) { - case 'success': - return 'confirmed' - case 'failed': - return 'failed' - case 'pending': - return 'pending' - default: - console.warn('WalletService: unrecognized payment status', payment.status) - return 'pending' + type: payment.amount > 0 ? 'received' : 'sent', + status: status, + fee: payment.fee_msat ? payment.fee_msat / 1000 : undefined, + tag: payment.tag || null } } }