Files
antonym/server/lib/btcpay.ts
Dorian 54500a68e6 feat: scaffold Antonym fashion store
Anonymous Bitcoin-only fashion e-commerce with:
- Vue 3 + Tailwind 4 frontend with glassmorphism dark/light design system
- Express 5 + SQLite backend with BTCPay Server integration
- Nostr identity (NIP-07/keypair) for anonymous purchase tracking
- ChaCha20-Poly1305 encrypted shipping addresses
- Admin panel with order/product/stock management
- SVG logo splash animation with clip-path reveal
- 5 seeded products across 4 categories

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 00:23:21 +00:00

51 lines
2.4 KiB
TypeScript

import crypto from 'node:crypto'
const TIMEOUT = 10_000
function getConfig() {
const url = process.env.BTCPAY_URL
const apiKey = process.env.BTCPAY_API_KEY
const storeId = process.env.BTCPAY_STORE_ID
if (!url || !apiKey || !storeId) throw new Error('BTCPay configuration missing: BTCPAY_URL, BTCPAY_API_KEY, BTCPAY_STORE_ID required')
return { url: url.replace(/\/$/, ''), apiKey, storeId }
}
async function btcpayFetch(path: string, options: RequestInit = {}): Promise<Response> {
const { url, apiKey } = getConfig()
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), TIMEOUT)
try {
return await fetch(`${url}${path}`, { ...options, signal: controller.signal, headers: { Authorization: `token ${apiKey}`, 'Content-Type': 'application/json', ...options.headers } })
} finally { clearTimeout(timer) }
}
export interface CreateInvoiceParams { amountSats: number; orderId: string; redirectUrl: string }
export interface BtcPayInvoice { id: string; checkoutLink: string; status: string; amount: string; currency: string }
export async function createInvoice(params: CreateInvoiceParams): Promise<BtcPayInvoice> {
const { storeId } = getConfig()
const res = await btcpayFetch(`/api/v1/stores/${storeId}/invoices`, {
method: 'POST',
body: JSON.stringify({ amount: String(params.amountSats), currency: 'SATS', metadata: { orderId: params.orderId }, checkout: { redirectURL: params.redirectUrl, redirectAutomatically: true } }),
})
if (!res.ok) { const text = await res.text(); throw new Error(`BTCPay invoice creation failed: ${res.status} ${text}`) }
return res.json() as Promise<BtcPayInvoice>
}
export async function getInvoice(invoiceId: string): Promise<BtcPayInvoice> {
const { storeId } = getConfig()
const res = await btcpayFetch(`/api/v1/stores/${storeId}/invoices/${invoiceId}`)
if (!res.ok) throw new Error(`BTCPay invoice fetch failed: ${res.status}`)
return res.json() as Promise<BtcPayInvoice>
}
export function validateWebhookSignature(body: string, signature: string): boolean {
const secret = process.env.BTCPAY_WEBHOOK_SECRET
if (!secret) return false
const expected = crypto.createHmac('sha256', secret).update(body).digest('hex')
const sigBuf = Buffer.from(signature.replace('sha256=', ''), 'hex')
const expectedBuf = Buffer.from(expected, 'hex')
if (sigBuf.length !== expectedBuf.length) return false
return crypto.timingSafeEqual(sigBuf, expectedBuf)
}