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
This commit is contained in:
drjones
2026-04-16 01:23:35 -07:00
parent 348e55b63e
commit 4354416f1c
5 changed files with 806 additions and 309 deletions

View File

@@ -13,6 +13,7 @@ interface WalletContextType {
earnLuxCredits: (amount: number) => void;
spendUsdStoreCredit: (amount: number) => boolean;
verifyBtcDeposit: (txid: string) => Promise<{ ok: boolean; error?: string; creditedUsd?: number }>;
creditBtcPayInvoice: (invoiceId: string, usdAmount: number) => { ok: boolean; error?: string };
vault: VaultState;
collectKey: (key: string) => boolean;
addVaultReceipt: (receipt: VaultReceipt) => void;
@@ -141,6 +142,23 @@ export const WalletProvider = ({ children }: WalletProviderProps) => {
[handle],
);
const creditBtcPayInvoice = useCallback(
(invoiceId: string, usdAmount: number) => {
if (!handle) return { ok: false, error: "Sign in first" };
const id = String(invoiceId || "").trim();
if (!id) return { ok: false, error: "Invalid invoice ID" };
const safe = Number.isFinite(usdAmount) ? Math.max(0, Math.round(usdAmount * 100) / 100) : 0;
if (safe <= 0) return { ok: false, error: "Invalid amount" };
const row = getLedgerRow(handle);
if (row.claimedTxids.includes(id)) return { ok: false, error: "Invoice already credited" };
const nextUsd = Math.round((row.usd + safe) * 100) / 100;
setLedgerRow(handle, { usd: nextUsd, lux: row.lux, claimedTxids: [...row.claimedTxids, id] });
setUsdStoreCredit(nextUsd);
return { ok: true };
},
[handle],
);
const collectKey = (key: string) => {
const trimmed = String(key || "").trim();
if (!trimmed) return false;
@@ -167,6 +185,7 @@ export const WalletProvider = ({ children }: WalletProviderProps) => {
earnLuxCredits,
spendUsdStoreCredit,
verifyBtcDeposit,
creditBtcPayInvoice,
vault,
collectKey,
addVaultReceipt,
@@ -179,6 +198,7 @@ export const WalletProvider = ({ children }: WalletProviderProps) => {
earnLuxCredits,
spendUsdStoreCredit,
verifyBtcDeposit,
creditBtcPayInvoice,
vault,
],
);