// payments.js import express from "express"; import Stripe from "stripe"; import { pool } from "./db.js"; import { sendDiscordOrder } from "./discord.js"; const router = express.Router(); const stripe = new Stripe(process.env.STRIPE_SECRET); router.post("/api/create-checkout", async (req, res) => { const user = req.session.user; if (!user) return res.sendStatus(401); const { items } = req.body; // (Optional) enforce again that requested server qty <= currently reserved const line_items = items.map(i => ({ quantity: i.qty, price_data: { currency: "usd", product_data: { name: i.name }, unit_amount: Math.round(i.price * 100) } })); const session = await stripe.checkout.sessions.create({ mode: "payment", line_items, success_url: process.env.SUCCESS_URL, cancel_url: process.env.CANCEL_URL, metadata: { discord_user_id: user.id, discord_username: user.username, items: JSON.stringify(items) } }); res.json({ url: session.url }); }); // Webhook: payment succeeded / refunded router.post("/api/stripe/webhook", express.raw({ type: "application/json" }), async (req, res) => { const sig = req.headers["stripe-signature"]; let evt; try { evt = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); } catch (e) { return res.status(400).send(`Webhook Error: ${e.message}`); } if (evt.type === "checkout.session.completed") { const s = evt.data.object; const items = JSON.parse(s.metadata.items); const total = items.reduce((a, i) => a + i.price * i.qty, 0); await pool.query( "INSERT INTO orders(id, discord_user_id, discord_username, line_items, total_cents, stripe_payment_intent, status) VALUES (gen_random_uuid(), $1,$2,$3,$4,$5,'paid')", [s.metadata.discord_user_id, s.metadata.discord_username, JSON.stringify(items), Math.round(total * 100), s.payment_intent] ); await sendDiscordOrder({ user: s.metadata, items, total }); } if (evt.type === "charge.refunded") { const pi = evt.data.object.payment_intent; // Find the order and mark refunded const { rows } = await pool.query("UPDATE orders SET status='refunded' WHERE stripe_payment_intent=$1 RETURNING line_items", [pi]); if (rows.length) { const items = rows[0].line_items; const returned = items.filter(i => serverIds.has(i.id)).reduce((a, i) => a + i.qty, 0); if (returned > 0) { await pool.query("UPDATE inventory_state SET value_int = value_int + $1 WHERE key='server_slots_remaining'", [returned]); } await sendDiscordOrder({ refund: true, items }); } } res.json({ received: true }); }); export default router;