/** * BTCPay Server Greenfield API v1 client. * * Env vars required (set in .env.local): * BTCPAY_URL e.g. http://10.30.20.237 * BTCPAY_API_KEY generated in BTCPay → Account → Manage account → API keys * BTCPAY_STORE_ID found in BTCPay → Settings → General (the alphanumeric ID in the URL) */ export type BtcPayInvoiceStatus = | "New" | "Processing" | "Expired" | "Invalid" | "Settled"; export type BtcPayInvoice = { id: string; status: BtcPayInvoiceStatus; amount: string; currency: string; checkoutLink: string; createdTime: number; expirationTime: number; metadata?: Record; }; export type BtcPayPaymentMethod = { paymentMethod: string; // "BTC" destination: string; // bc1q... address paymentLink: string; // bitcoin:... URI rate: string; // BTC/USD rate at time of invoice due: string; // BTC amount customer must send amount: string; // total BTC amount totalPaid: string; // BTC paid so far }; function btcPayConfig() { const url = (process.env.BTCPAY_URL ?? "").replace(/\/$/, ""); const apiKey = process.env.BTCPAY_API_KEY ?? ""; const storeId = process.env.BTCPAY_STORE_ID ?? ""; return { url, apiKey, storeId }; } export function isBtcPayConfigured(): boolean { const { url, apiKey, storeId } = btcPayConfig(); return Boolean(url && apiKey && storeId); } function authHeaders(apiKey: string) { return { "Content-Type": "application/json", Authorization: `token ${apiKey}`, }; } /** * Create a new BTCPay invoice. * `usdAmount` is the dollar amount the customer wants to deposit. * BTCPay converts this to the BTC equivalent at current rate. */ export async function createBtcPayInvoice( usdAmount: number, handle: string, ): Promise<{ ok: true; invoice: BtcPayInvoice } | { ok: false; error: string }> { const { url, apiKey, storeId } = btcPayConfig(); if (!url || !apiKey || !storeId) { return { ok: false, error: "BTCPay not configured — set BTCPAY_URL, BTCPAY_API_KEY, BTCPAY_STORE_ID" }; } try { const res = await fetch(`${url}/api/v1/stores/${storeId}/invoices`, { method: "POST", headers: authHeaders(apiKey), body: JSON.stringify({ amount: usdAmount.toFixed(2), currency: "USD", metadata: { cyberlux_handle: handle, source: "cyberlux-deposit-desk", }, checkout: { speedPolicy: "LowSpeed", // 1 confirmation required paymentMethods: ["BTC"], defaultPaymentMethod: "BTC", expirationMinutes: 60, }, }), cache: "no-store", }); if (!res.ok) { const text = await res.text().catch(() => ""); return { ok: false, error: `BTCPay error ${res.status}: ${text.slice(0, 120)}` }; } const invoice = (await res.json()) as BtcPayInvoice; return { ok: true, invoice }; } catch (err) { return { ok: false, error: `Network error reaching BTCPay: ${String(err).slice(0, 100)}` }; } } /** * Fetch current status and payment method details for an invoice. */ export async function getBtcPayInvoiceStatus(invoiceId: string): Promise<{ ok: true; status: BtcPayInvoiceStatus; usdAmount: number; paymentMethod?: BtcPayPaymentMethod; } | { ok: false; error: string }> { const { url, apiKey, storeId } = btcPayConfig(); if (!url || !apiKey || !storeId) { return { ok: false, error: "BTCPay not configured" }; } try { // Fetch invoice const invRes = await fetch(`${url}/api/v1/stores/${storeId}/invoices/${invoiceId}`, { headers: authHeaders(apiKey), cache: "no-store", }); if (!invRes.ok) { return { ok: false, error: `Invoice not found (${invRes.status})` }; } const invoice = (await invRes.json()) as BtcPayInvoice; // Fetch payment methods to get the BTC address const pmRes = await fetch( `${url}/api/v1/stores/${storeId}/invoices/${invoiceId}/payment-methods`, { headers: authHeaders(apiKey), cache: "no-store" }, ); let paymentMethod: BtcPayPaymentMethod | undefined; if (pmRes.ok) { const methods = (await pmRes.json()) as BtcPayPaymentMethod[]; paymentMethod = methods.find((m) => m.paymentMethod === "BTC"); } return { ok: true, status: invoice.status, usdAmount: parseFloat(invoice.amount), paymentMethod, }; } catch (err) { return { ok: false, error: `Network error: ${String(err).slice(0, 100)}` }; } }