import { randomBytes } from "crypto"; export interface LNbitsInvoice { paymentHash: string; paymentRequest: string; } export interface LNbitsConfig { url: string; apiKey: string; } const stubPaidInvoices = new Set(); export class LNbitsService { private readonly url: string; private readonly apiKey: string; readonly stubMode: boolean; constructor(config?: Partial) { this.url = config?.url ?? process.env.LNBITS_URL ?? ""; this.apiKey = config?.apiKey ?? process.env.LNBITS_API_KEY ?? ""; this.stubMode = !this.url || !this.apiKey; if (this.stubMode) { console.warn("[LNbitsService] No LNBITS_URL/LNBITS_API_KEY — running in STUB mode. Invoices are simulated."); } } async createInvoice(amountSats: number, memo: string): Promise { if (this.stubMode) { const paymentHash = randomBytes(32).toString("hex"); const paymentRequest = `lnbcrt${amountSats}u1stub_${paymentHash.slice(0, 16)}`; console.log(`[stub] Created invoice: ${amountSats} sats — "${memo}" — hash=${paymentHash}`); return { paymentHash, paymentRequest }; } const response = await fetch(`${this.url.replace(/\/$/, "")}/api/v1/payments`, { method: "POST", headers: this.headers(), body: JSON.stringify({ out: false, amount: amountSats, memo }), }); if (!response.ok) { const body = await response.text(); throw new Error(`LNbits createInvoice failed (${response.status}): ${body}`); } const data = (await response.json()) as { payment_hash: string; payment_request: string; }; return { paymentHash: data.payment_hash, paymentRequest: data.payment_request }; } async checkInvoicePaid(paymentHash: string): Promise { if (this.stubMode) { return stubPaidInvoices.has(paymentHash); } const response = await fetch( `${this.url.replace(/\/$/, "")}/api/v1/payments/${paymentHash}`, { method: "GET", headers: this.headers() }, ); if (!response.ok) { const body = await response.text(); throw new Error(`LNbits checkInvoice failed (${response.status}): ${body}`); } const data = (await response.json()) as { paid: boolean }; return data.paid; } /** Stub-only helper: mark an invoice as paid for testing/dev flows. */ stubMarkPaid(paymentHash: string): void { if (!this.stubMode) { throw new Error("stubMarkPaid called on a real LNbitsService instance"); } stubPaidInvoices.add(paymentHash); console.log(`[stub] Marked invoice paid: hash=${paymentHash}`); } private headers(): Record { return { "Content-Type": "application/json", "X-Api-Key": this.apiKey, }; } } export const lnbitsService = new LNbitsService();