- Update commented TODO in PaymentMonitorService to use proper DI pattern - Remove outdated reference to legacy invoiceService singleton - Prepare PaymentMonitorService for future DI migration with proper pattern This cleanup eliminates the last remaining reference to the legacy InvoiceService singleton pattern, ensuring code consistency. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
276 lines
8 KiB
TypeScript
276 lines
8 KiB
TypeScript
import { ref } from 'vue'
|
|
import type { PaymentStatus, LightningInvoice } from '@/core/services/invoiceService'
|
|
import type { Order } from '@/stores/market'
|
|
|
|
export interface PaymentMonitorState {
|
|
isMonitoring: boolean
|
|
activeInvoices: Map<string, LightningInvoice>
|
|
paymentStatuses: Map<string, PaymentStatus>
|
|
lastUpdate: number
|
|
error: string | null
|
|
}
|
|
|
|
export interface PaymentUpdate {
|
|
orderId: string
|
|
paymentHash: string
|
|
status: 'pending' | 'paid' | 'expired'
|
|
amount: number
|
|
paidAt?: number
|
|
}
|
|
|
|
class PaymentMonitorService {
|
|
private state = ref<PaymentMonitorState>({
|
|
isMonitoring: false,
|
|
activeInvoices: new Map(),
|
|
paymentStatuses: new Map(),
|
|
lastUpdate: 0,
|
|
error: null
|
|
})
|
|
|
|
private monitoringInterval: number | null = null
|
|
private updateCallbacks: Map<string, (update: PaymentUpdate) => void> = new Map()
|
|
|
|
// 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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<PaymentStatus> {
|
|
try {
|
|
// For now, we'll simulate payment status checking since we don't have wallet context here
|
|
// In production, this would integrate with LNBits webhooks or polling
|
|
// TODO: Pass wallet information from the order context
|
|
console.log('Payment status check requested for:', paymentHash)
|
|
|
|
// Return default pending status for now
|
|
return {
|
|
paid: false,
|
|
amount_paid: 0,
|
|
payment_hash: paymentHash
|
|
}
|
|
|
|
// TODO: Implement when wallet context is available and PaymentMonitorService is migrated to DI:
|
|
// const invoiceService = injectService(SERVICE_TOKENS.INVOICE_SERVICE) as InvoiceService
|
|
// const status = await invoiceService.checkPaymentStatus(paymentHash, adminKey)
|
|
// return status
|
|
} 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<void> {
|
|
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')
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const paymentMonitor = new PaymentMonitorService()
|
|
|