import { Router } from 'express' import { getDb } from '../db/connection.js' import { validateWebhookSignature } from '../lib/btcpay.js' import { sendOrderConfirmation } from '../lib/mailer.js' import { notifyOrderConfirmed } from '../lib/nostr.js' import type { SizeStock } from '../../shared/types.js' export const webhooksRouter = Router() interface OrderRow { id: string; nostr_pubkey: string | null; email: string | null; items: string; total_sats: number; status: string } webhooksRouter.post('/btcpay', async (req, res) => { try { const signature = req.headers['btcpay-sig'] as string | undefined const rawBody = JSON.stringify(req.body) if (!signature || !validateWebhookSignature(rawBody, signature)) { res.status(401).json({ error: { code: 'INVALID_SIGNATURE', message: 'Invalid webhook signature' } }); return } const { type, invoiceId } = req.body as { type: string; invoiceId: string } const db = getDb() const order = db.prepare('SELECT * FROM orders WHERE btcpay_invoice_id = ?').get(invoiceId) as OrderRow | undefined if (!order) { res.status(404).json({ error: { code: 'ORDER_NOT_FOUND', message: 'Order not found for invoice' } }); return } if (type === 'InvoiceSettled' || type === 'InvoicePaymentSettled') { if (order.status !== 'pending') { res.json({ ok: true, message: 'Already processed' }); return } const items = JSON.parse(order.items) as { productId: string; size: string; quantity: number }[] const decrementStock = db.transaction(() => { for (const item of items) { const product = db.prepare('SELECT sizes FROM products WHERE id = ?').get(item.productId) as { sizes: string } | undefined if (!product) continue const sizes = JSON.parse(product.sizes) as SizeStock[] const sizeEntry = sizes.find((s) => s.size === item.size) if (sizeEntry) sizeEntry.stock = Math.max(0, sizeEntry.stock - item.quantity) db.prepare("UPDATE products SET sizes = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(sizes), item.productId) } db.prepare("UPDATE orders SET status = ?, updated_at = datetime('now') WHERE id = ?").run('paid', order.id) db.prepare('INSERT INTO order_events (order_id, status, note) VALUES (?, ?, ?)').run(order.id, 'paid', 'Payment confirmed via BTCPay') }) decrementStock() if (order.email) sendOrderConfirmation(order.email, order.id, order.total_sats).catch(() => {}) if (order.nostr_pubkey) notifyOrderConfirmed(order.nostr_pubkey, order.id, order.total_sats).catch(() => {}) } else if (type === 'InvoiceExpired') { db.prepare("UPDATE orders SET status = ?, updated_at = datetime('now') WHERE id = ?").run('cancelled', order.id) db.prepare('INSERT INTO order_events (order_id, status, note) VALUES (?, ?, ?)').run(order.id, 'cancelled', 'Invoice expired') } res.json({ ok: true }) } catch (err) { console.error('Webhook processing failed:', err); res.status(500).json({ error: { code: 'WEBHOOK_FAILED', message: 'Webhook processing failed' } }) } })