import { getApiUrl } from '@/lib/config/lnbits' import type { Order } from '@/modules/market/stores/market' export interface LightningInvoice { checking_id: string payment_hash: string wallet_id: string amount: number fee: number bolt11: string // This is the payment request/invoice status: string memo?: string expiry?: string preimage?: string extra?: Record created_at?: string updated_at?: string } export interface CreateInvoiceRequest { amount: number memo: string unit?: 'sat' | 'btc' expiry?: number extra?: Record } export interface PaymentStatus { paid: boolean amount_paid: number paid_at?: number payment_hash: string } export class InvoiceService { private baseUrl: string constructor() { // Use the payments endpoint for invoice creation this.baseUrl = getApiUrl('/payments') } private async request( endpoint: string, adminKey: string, options: RequestInit = {} ): Promise { // Construct the URL - for payments, we just append the endpoint const url = `${this.baseUrl}${endpoint}` console.log('Invoice Service Request:', { url, endpoint }) const headers: HeadersInit = { 'Content-Type': 'application/json', 'X-Api-Key': adminKey, // Use the wallet's admin key ...options.headers, } const response = await fetch(url, { ...options, headers, }) if (!response.ok) { const errorText = await response.text() console.error('Invoice Service Error:', { status: response.status, statusText: response.statusText, errorText, url }) throw new Error(`Invoice request failed: ${response.status} ${response.statusText}`) } return response.json() } /** * Create a Lightning invoice for an order */ async createInvoice(order: Order, adminKey: string, extra?: Record): Promise { const invoiceData: CreateInvoiceRequest = { amount: order.total, unit: 'sat', memo: `Order ${order.id} - ${order.items.length} items`, expiry: extra?.expiry || 3600, // Allow configurable expiry, default 1 hour extra: { tag: 'nostrmarket', // Use nostrmarket tag for compatibility order_id: extra?.order_id || order.id, // Use passed order_id or fallback to order.id merchant_pubkey: extra?.merchant_pubkey || order.sellerPubkey, // Use passed merchant_pubkey or fallback ...extra // Allow additional metadata to be passed in } } try { // Log the exact data being sent to LNBits const requestBody = { out: false, // Incoming payment ...invoiceData } console.log('Sending invoice request to LNBits:', { url: `${this.baseUrl}`, body: requestBody, extra: requestBody.extra }) // Use the correct LNBits payments endpoint const response = await this.request('', adminKey, { method: 'POST', body: JSON.stringify(requestBody) }) console.log('Full LNBits response:', response) console.log('Response type:', typeof response) console.log('Response keys:', Object.keys(response)) console.log('Response expiry field:', response.expiry) console.log('Response created_at field:', response.created_at) // Check if we have the expected fields if (!response.bolt11) { console.error('Missing bolt11 in response:', response) throw new Error('Invalid invoice response: missing bolt11') } console.log('Lightning invoice created with nostrmarket tag:', { orderId: order.id, paymentHash: response.payment_hash, amount: response.amount, paymentRequest: response.bolt11.substring(0, 50) + '...', extra: invoiceData.extra }) return response } catch (error) { console.error('Failed to create Lightning invoice:', error) throw new Error('Failed to create payment invoice') } } /** * Check payment status of an invoice */ async checkPaymentStatus(paymentHash: string, adminKey: string): Promise { try { const response = await this.request(`/${paymentHash}`, adminKey, {}) return response } catch (error) { console.error('Failed to check payment status:', error) throw new Error('Failed to check payment status') } } /** * Get all payments for a wallet */ async getWalletPayments(adminKey: string, limit: number = 100): Promise { try { const response = await this.request(`?limit=${limit}`, adminKey, {}) return response } catch (error) { console.error('Failed to get wallet payments:', error) throw new Error('Failed to get wallet payments') } } /** * Validate a Lightning payment request */ validatePaymentRequest(paymentRequest: string): boolean { // Basic validation - should start with 'lnbc' and be a valid length return paymentRequest.startsWith('lnbc') && paymentRequest.length > 50 } /** * Extract payment hash from a payment request */ extractPaymentHash(paymentRequest: string): string | null { try { // This is a simplified extraction - in production you'd use a proper BOLT11 decoder const match = paymentRequest.match(/lnbc[0-9]+[a-z0-9]+/i) return match ? match[0] : null } catch (error) { console.error('Failed to extract payment hash:', error) return null } } }