78 lines
2.9 KiB
JavaScript
78 lines
2.9 KiB
JavaScript
// 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;
|