import { Router } from 'express' import { nanoid } from 'nanoid' import { getDb } from '../db/connection.js' import { encrypt } from '../lib/crypto.js' import { createInvoice } from '../lib/btcpay.js' import { requireBody, sanitizeString } from '../middleware/validate.js' import type { CreateOrderRequest, Order, OrderItem, OrderEvent, OrderStatus } from '../../shared/types.js' export const ordersRouter = Router() interface OrderRow { id: string; nostr_pubkey: string | null; email: string | null; btcpay_invoice_id: string | null; status: string; shipping_address_encrypted: string | null; items: string; total_sats: number; note: string | null; created_at: string; updated_at: string } function rowToOrder(row: OrderRow): Order { return { id: row.id, nostrPubkey: row.nostr_pubkey, email: row.email, btcpayInvoiceId: row.btcpay_invoice_id, status: row.status as OrderStatus, items: JSON.parse(row.items) as OrderItem[], totalSats: row.total_sats, note: row.note, createdAt: row.created_at, updatedAt: row.updated_at } } ordersRouter.post('/', requireBody('items', 'shippingAddress'), async (req, res) => { try { const body = req.body as CreateOrderRequest const db = getDb() if (!Array.isArray(body.items) || body.items.length === 0) { res.status(400).json({ error: { code: 'INVALID_ITEMS', message: 'Order must contain items' } }); return } const orderItems: OrderItem[] = [] let totalSats = 0 for (const item of body.items) { const product = db.prepare('SELECT * FROM products WHERE id = ? AND is_active = 1').get(item.productId) as { name: string; price_sats: number; sizes: string } | undefined if (!product) { res.status(400).json({ error: { code: 'INVALID_PRODUCT', message: `Product ${item.productId} not found` } }); return } const sizes = JSON.parse(product.sizes) as { size: string; stock: number }[] const sizeEntry = sizes.find((s) => s.size === item.size) if (!sizeEntry || sizeEntry.stock < item.quantity) { res.status(400).json({ error: { code: 'INSUFFICIENT_STOCK', message: `Insufficient stock for ${product.name} size ${item.size}` } }); return } const lineTotal = product.price_sats * item.quantity totalSats += lineTotal orderItems.push({ productId: item.productId, productName: product.name, size: item.size, quantity: item.quantity, priceSats: product.price_sats }) } const orderId = nanoid() const encryptedAddress = encrypt(JSON.stringify(body.shippingAddress)) const siteUrl = process.env.SITE_URL || 'http://localhost:3333' const invoice = await createInvoice({ amountSats: totalSats, orderId, redirectUrl: `${siteUrl}/order/${orderId}` }) db.prepare('INSERT INTO orders (id, nostr_pubkey, email, btcpay_invoice_id, status, shipping_address_encrypted, items, total_sats, note) VALUES (?, ?, ?, ?, \'pending\', ?, ?, ?, ?)').run(orderId, body.nostrPubkey ? sanitizeString(body.nostrPubkey) : null, body.email ? sanitizeString(body.email) : null, invoice.id, encryptedAddress, JSON.stringify(orderItems), totalSats, body.note ? sanitizeString(body.note) : null) db.prepare('INSERT INTO order_events (order_id, status, note) VALUES (?, ?, ?)').run(orderId, 'pending', 'Order created') res.status(201).json({ orderId, invoiceUrl: invoice.checkoutLink, invoiceId: invoice.id }) } catch (err) { console.error('Order creation failed:', err); res.status(500).json({ error: { code: 'ORDER_FAILED', message: 'Failed to create order' } }) } }) ordersRouter.get('/:id', (req, res) => { const db = getDb() const row = db.prepare('SELECT * FROM orders WHERE id = ?').get(req.params.id) as OrderRow | undefined if (!row) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Order not found' } }); return } const order = rowToOrder(row) const events = db.prepare('SELECT * FROM order_events WHERE order_id = ? ORDER BY created_at ASC').all(req.params.id) as OrderEvent[] res.json({ ...order, events }) })