dok/routes/shop.route.js
2026-04-11 15:28:32 +01:00

153 lines
5.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
routes/shop.route.js
Stripe Shop Gems kaufen
.env benötigt:
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
APP_URL=https://spiel.dynastyofknights.com
============================================================ */
const express = require("express");
const router = express.Router();
const db = require("../database/database");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
function requireLogin(req, res, next) {
if (!req.session?.user) return res.status(401).json({ error: "Nicht eingeloggt" });
next();
}
/* ════════════════════════════════════════════
Shop-Pakete (Preis in Cent!)
════════════════════════════════════════════ */
const PACKAGES = [
{ id: "starter", name: "Starter", gems: 100, price: 199, bonus: 0 },
{ id: "abenteurer", name: "Abenteurer", gems: 500, price: 799, bonus: 50 },
{ id: "ritter", name: "Ritter", gems: 1000, price: 1499, bonus: 150 },
{ id: "fuerst", name: "Fürst", gems: 2500, price: 3499, bonus: 500 },
{ id: "koenig", name: "König", gems: 5000, price: 5999, bonus: 1500 },
];
/* ════════════════════════════════════════════
GET /api/shop/packages
Pakete für Frontend laden
════════════════════════════════════════════ */
router.get("/shop/packages", requireLogin, (req, res) => {
res.json(PACKAGES);
});
/* ════════════════════════════════════════════
POST /api/shop/checkout
Stripe Checkout Session erstellen
Body: { packageId: "ritter" }
════════════════════════════════════════════ */
router.post("/shop/checkout", requireLogin, async (req, res) => {
const userId = req.session.user.id;
const { packageId } = req.body;
const pkg = PACKAGES.find(p => p.id === packageId);
if (!pkg) return res.status(400).json({ error: "Paket nicht gefunden." });
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card", "paypal"],
line_items: [{
price_data: {
currency: "eur",
product_data: {
name: `${pkg.name} Paket ${pkg.gems + pkg.bonus} 💠 Gems`,
description: pkg.bonus > 0
? `${pkg.gems} Gems + ${pkg.bonus} Bonus-Gems`
: `${pkg.gems} Gems`,
images: [`${process.env.APP_URL}/images/items/blauer-cristal.png`],
},
unit_amount: pkg.price,
},
quantity: 1,
}],
mode: "payment",
success_url: `${process.env.APP_URL}/launcher?payment=success`,
cancel_url: `${process.env.APP_URL}/launcher?payment=cancel`,
metadata: {
userId: String(userId),
packageId: pkg.id,
gems: String(pkg.gems + pkg.bonus),
},
});
res.json({ url: session.url });
} catch (err) {
console.error("Stripe Fehler:", err);
res.status(500).json({ error: "Zahlung konnte nicht gestartet werden." });
}
});
/* ════════════════════════════════════════════
POST /api/shop/webhook
Stripe Webhook Zahlung bestätigt
WICHTIG: Muss VOR express.json() registriert
werden → raw body nötig!
════════════════════════════════════════════ */
router.post(
"/shop/webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error("Webhook Signatur-Fehler:", err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
const userId = parseInt(session.metadata.userId);
const gems = parseInt(session.metadata.gems);
const packageId = session.metadata.packageId;
try {
/* ── Idempotenz: bereits verarbeitet? ── */
const [[existing]] = await db.query(
"SELECT id FROM shop_purchases WHERE stripe_session_id = ?",
[session.id]
);
if (existing) {
console.log(`⚠️ Webhook bereits verarbeitet (ignoriert): ${session.id}`);
return res.json({ received: true });
}
/* ── Gems gutschreiben ── */
await db.query(
"UPDATE account_currency SET gems = gems + ? WHERE account_id = ?",
[gems, userId]
);
/* ── Kauf protokollieren ── */
await db.query(
`INSERT INTO shop_purchases
(user_id, package_id, gems, stripe_session_id, created_at)
VALUES (?, ?, ?, ?, NOW())`,
[userId, packageId, gems, session.id]
);
console.log(`✅ Shop: User ${userId} erhält ${gems} Gems (${packageId})`);
} catch (err) {
console.error("Gems gutschreiben Fehler:", err);
return res.status(500).end();
}
}
res.json({ received: true });
}
);
module.exports = router;