import { ref } from 'vue' import { BaseService } from '@/core/base/BaseService' import type { PaymentStatus, LightningInvoice } from '@/core/services/invoiceService' import type { Order } from '@/modules/market/stores/market' export interface PaymentMonitorState { isMonitoring: boolean activeInvoices: Map paymentStatuses: Map lastUpdate: number error: string | null } export interface PaymentUpdate { orderId: string paymentHash: string status: 'pending' | 'paid' | 'expired' amount: number paidAt?: number } export class PaymentMonitorService extends BaseService { // Service metadata protected readonly metadata = { name: 'PaymentMonitorService', version: '1.0.0', dependencies: ['InvoiceService'] // Only depends on InvoiceService for payment status checking } private state = ref({ isMonitoring: false, activeInvoices: new Map(), paymentStatuses: new Map(), lastUpdate: 0, error: null }) private monitoringInterval: number | null = null private updateCallbacks: Map void> = new Map() /** * Service-specific initialization (called by BaseService) */ protected async onInitialize(): Promise { this.debug('PaymentMonitorService initialized') // No special initialization needed } // Computed properties get isMonitoring() { return this.state.value.isMonitoring } get activeInvoices() { return this.state.value.activeInvoices } get paymentStatuses() { return this.state.value.paymentStatuses } get lastUpdate() { return this.state.value.lastUpdate } get error() { return this.state.value.error } /** * Start monitoring payments for a specific order */ async startMonitoring(order: Order, invoice: LightningInvoice): Promise { try { // Add invoice to active monitoring this.state.value.activeInvoices.set(order.id, invoice) this.state.value.paymentStatuses.set(invoice.payment_hash, { paid: false, amount_paid: 0, payment_hash: invoice.payment_hash }) // Start monitoring if not already running if (!this.state.value.isMonitoring) { await this.startMonitoringLoop() } console.log('Started monitoring payment for order:', { orderId: order.id, paymentHash: invoice.payment_hash, amount: invoice.amount }) } catch (error) { console.error('Failed to start payment monitoring:', error) this.state.value.error = 'Failed to start payment monitoring' } } /** * Stop monitoring a specific order */ stopMonitoring(orderId: string): void { const invoice = this.state.value.activeInvoices.get(orderId) if (invoice) { this.state.value.activeInvoices.delete(orderId) this.state.value.paymentStatuses.delete(invoice.payment_hash) console.log('Stopped monitoring payment for order:', orderId) } // Stop monitoring loop if no more active invoices if (this.state.value.activeInvoices.size === 0) { this.stopMonitoringLoop() } } /** * Start the monitoring loop */ private async startMonitoringLoop(): Promise { if (this.state.value.isMonitoring) return this.state.value.isMonitoring = true console.log('Starting payment monitoring loop') // Check immediately await this.checkAllPayments() // Set up interval for periodic checks this.monitoringInterval = setInterval(async () => { await this.checkAllPayments() }, 30000) as unknown as number // Check every 30 seconds } /** * Stop the monitoring loop */ private stopMonitoringLoop(): void { if (this.monitoringInterval) { clearInterval(this.monitoringInterval) this.monitoringInterval = null } this.state.value.isMonitoring = false console.log('Stopped payment monitoring loop') } /** * Check payment status for all active invoices */ private async checkAllPayments(): Promise { try { this.state.value.error = null this.state.value.lastUpdate = Date.now() const promises = Array.from(this.state.value.activeInvoices.entries()).map( async ([orderId, invoice]) => { try { // Get payment status from LNBits const status = await this.getPaymentStatus(invoice.payment_hash) // Update local status this.state.value.paymentStatuses.set(invoice.payment_hash, status) // Check if status changed const previousStatus = this.state.value.paymentStatuses.get(invoice.payment_hash) if (previousStatus && previousStatus.paid !== status.paid) { await this.handlePaymentStatusChange(orderId, invoice, status) } return { orderId, status } } catch (error) { console.error(`Failed to check payment for order ${orderId}:`, error) return { orderId, error } } } ) await Promise.allSettled(promises) } catch (error) { console.error('Payment monitoring error:', error) this.state.value.error = 'Payment monitoring failed' } } /** * Get payment status from LNBits */ private async getPaymentStatus(paymentHash: string): Promise { try { // Use injected InvoiceService for payment status checking if (!this.invoiceService) { console.warn('InvoiceService not available, returning default pending status') return { paid: false, amount_paid: 0, payment_hash: paymentHash } } console.log('Checking payment status for:', paymentHash) // TODO: Need to pass adminKey from order context - for now return pending // In a complete implementation, we'd store wallet context with each order // const status = await this.invoiceService.checkPaymentStatus(paymentHash, adminKey) // return status // Return default pending status until wallet context is available return { paid: false, amount_paid: 0, payment_hash: paymentHash } } catch (error) { console.error('Failed to get payment status:', error) // Return default pending status return { paid: false, amount_paid: 0, payment_hash: paymentHash } } } /** * Handle payment status changes */ private async handlePaymentStatusChange( orderId: string, invoice: LightningInvoice, status: PaymentStatus ): Promise { const update: PaymentUpdate = { orderId, paymentHash: invoice.payment_hash, status: status.paid ? 'paid' : 'pending', amount: invoice.amount, paidAt: status.paid_at } console.log('Payment status changed:', update) // Notify callbacks const callback = this.updateCallbacks.get(orderId) if (callback) { try { callback(update) } catch (error) { console.error('Payment update callback error:', error) } } // If payment is complete, stop monitoring this order if (status.paid) { this.stopMonitoring(orderId) } } /** * Register a callback for payment updates */ onPaymentUpdate(orderId: string, callback: (update: PaymentUpdate) => void): void { this.updateCallbacks.set(orderId, callback) } /** * Unregister a payment update callback */ offPaymentUpdate(orderId: string): void { this.updateCallbacks.delete(orderId) } /** * Get current payment status for an order */ getOrderPaymentStatus(orderId: string): PaymentStatus | null { const invoice = this.state.value.activeInvoices.get(orderId) if (!invoice) return null return this.state.value.paymentStatuses.get(invoice.payment_hash) || null } /** * Check if an order payment is complete */ isOrderPaid(orderId: string): boolean { const status = this.getOrderPaymentStatus(orderId) return status?.paid || false } /** * Get all pending payments */ getPendingPayments(): Array<{ orderId: string; invoice: LightningInvoice }> { return Array.from(this.state.value.activeInvoices.entries()) .filter(([orderId]) => !this.isOrderPaid(orderId)) .map(([orderId, invoice]) => ({ orderId, invoice })) } /** * Clean up resources */ cleanup(): void { this.stopMonitoringLoop() this.state.value.activeInvoices.clear() this.state.value.paymentStatuses.clear() this.updateCallbacks.clear() this.state.value.error = null console.log('Payment monitor cleaned up') } } // Service is now registered in the DI container