Files
dark-lord/app/api/btcpay/invoice/route.ts
drjones 4354416f1c Integrate BTCPay Server: auto-settling invoice deposits
- lib/btcpay.ts: Greenfield API client (createBtcPayInvoice, getBtcPayInvoiceStatus)
- /api/btcpay/invoice: creates per-deposit invoice (USD amount + handle in metadata)
- /api/btcpay/status/[id]: polls invoice status + returns BTC address/amount
- add-funds page: BTCPay tab with amount picker, live address display,
  10s auto-poll, settlement auto-credits localStorage ledger
- WalletContext: creditBtcPayInvoice() with duplicate-invoice guard
- .env.example: BTCPAY_URL, BTCPAY_API_KEY, BTCPAY_STORE_ID, NEXT_PUBLIC_BTCPAY_ENABLED

Made-with: Cursor
2026-04-16 01:23:35 -07:00

45 lines
1.6 KiB
TypeScript

import { NextResponse } from "next/server";
import { createBtcPayInvoice, isBtcPayConfigured } from "@/lib/btcpay";
export async function POST(req: Request) {
if (!isBtcPayConfigured()) {
return NextResponse.json(
{ ok: false, error: "BTCPay not configured — set BTCPAY_URL, BTCPAY_API_KEY, BTCPAY_STORE_ID in .env.local" },
{ status: 503 },
);
}
let body: unknown;
try { body = await req.json(); } catch {
return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400 });
}
const { usdAmount, handle } = (body as Record<string, unknown>) ?? {};
if (typeof usdAmount !== "number" || usdAmount <= 0 || !Number.isFinite(usdAmount)) {
return NextResponse.json({ ok: false, error: "usdAmount must be a positive number" }, { status: 400 });
}
if (typeof handle !== "string" || !handle.trim()) {
return NextResponse.json({ ok: false, error: "handle is required" }, { status: 400 });
}
if (usdAmount < 0.5) {
return NextResponse.json({ ok: false, error: "Minimum deposit is $0.50" }, { status: 400 });
}
if (usdAmount > 50000) {
return NextResponse.json({ ok: false, error: "Maximum single deposit is $50,000" }, { status: 400 });
}
const result = await createBtcPayInvoice(usdAmount, handle.trim());
if (!result.ok) {
return NextResponse.json({ ok: false, error: result.error }, { status: 502 });
}
return NextResponse.json({
ok: true,
invoiceId: result.invoice.id,
checkoutLink: result.invoice.checkoutLink,
status: result.invoice.status,
expiresAt: result.invoice.expirationTime,
});
}