153 lines
5.7 KiB
JavaScript
153 lines
5.7 KiB
JavaScript
/* ============================================================
|
||
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;
|