diff --git a/schema.sql b/schema.sql index 0c40085..c4f64e5 100644 --- a/schema.sql +++ b/schema.sql @@ -8,15 +8,42 @@ CREATE TABLE IF NOT EXISTS users ( updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); +CREATE TABLE IF NOT EXISTS promo_codes ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(32) NOT NULL UNIQUE, + bonus_type VARCHAR(16) NOT NULL DEFAULT 'fixed', + bonus_value INT NOT NULL DEFAULT 0, + max_uses INT NULL, + expires_at DATETIME NULL, + active TINYINT(1) NOT NULL DEFAULT 1, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + CREATE TABLE IF NOT EXISTS deposits ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL, amount INT NOT NULL, + bonus_amount INT NOT NULL DEFAULT 0, + promo_code_id BIGINT UNSIGNED NULL, stripe_payment_intent_id VARCHAR(128) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'created', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_deposits_user_id FOREIGN KEY (user_id) REFERENCES users(id) + CONSTRAINT fk_deposits_user_id FOREIGN KEY (user_id) REFERENCES users(id), + CONSTRAINT fk_deposits_promo_code_id FOREIGN KEY (promo_code_id) REFERENCES promo_codes(id) ); CREATE INDEX idx_deposits_stripe ON deposits (stripe_payment_intent_id); + +CREATE TABLE IF NOT EXISTS promo_redemptions ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT UNSIGNED NOT NULL, + promo_code_id BIGINT UNSIGNED NOT NULL, + deposit_id BIGINT UNSIGNED NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_redemptions_user_id FOREIGN KEY (user_id) REFERENCES users(id), + CONSTRAINT fk_redemptions_promo_code_id FOREIGN KEY (promo_code_id) REFERENCES promo_codes(id), + CONSTRAINT fk_redemptions_deposit_id FOREIGN KEY (deposit_id) REFERENCES deposits(id), + UNIQUE KEY uniq_user_promo (user_id, promo_code_id) +); diff --git a/src/routes/wallet.js b/src/routes/wallet.js index db3bc44..a841f9f 100644 --- a/src/routes/wallet.js +++ b/src/routes/wallet.js @@ -19,6 +19,27 @@ function ensureStripeConfigured(res) { return true; } +async function getPromoCode(code) { + const rows = await query( + 'SELECT id, code, bonus_type, bonus_value, max_uses, expires_at, active FROM promo_codes WHERE code = ? AND active = 1 AND (expires_at IS NULL OR expires_at > NOW())', + [code] + ); + return rows[0]; +} + +function calcBonusAmount(amount, promo) { + if (!promo) { + return 0; + } + if (promo.bonus_type === 'percent') { + return Math.max(0, Math.floor(amount * Number(promo.bonus_value) / 100)); + } + if (promo.bonus_type === 'fixed') { + return Math.max(0, Number(promo.bonus_value)); + } + return 0; +} + router.post('/api/wallet/deposit-intent', authMiddleware, async (req, res) => { try { if (!ensureStripeConfigured(res)) { @@ -36,6 +57,39 @@ router.post('/api/wallet/deposit-intent', authMiddleware, async (req, res) => { return res.status(400).json({ error: `A feltoltes ${MIN_DEPOSIT} es ${MAX_DEPOSIT} Ft kozott lehet.` }); } + const promoInput = req.body.promoCode?.toString().trim().toUpperCase(); + let promo = null; + let bonusAmount = 0; + let promoCodeId = null; + + if (promoInput) { + promo = await getPromoCode(promoInput); + if (!promo) { + return res.status(400).json({ error: 'Ervenytelen promo kod.' }); + } + + const redeemed = await query( + 'SELECT id FROM promo_redemptions WHERE user_id = ? AND promo_code_id = ? LIMIT 1', + [req.userId, promo.id] + ); + if (redeemed.length > 0) { + return res.status(400).json({ error: 'A promo kod mar felhasznalva.' }); + } + + if (promo.max_uses) { + const usage = await query( + 'SELECT COUNT(*) AS count FROM promo_redemptions WHERE promo_code_id = ?', + [promo.id] + ); + if ((usage[0]?.count ?? 0) >= promo.max_uses) { + return res.status(400).json({ error: 'A promo kod mar elfogyott.' }); + } + } + + bonusAmount = calcBonusAmount(amount, promo); + promoCodeId = promo.id; + } + const paymentIntent = await stripe.paymentIntents.create({ amount: stripeAmount, currency: 'huf', @@ -46,8 +100,8 @@ router.post('/api/wallet/deposit-intent', authMiddleware, async (req, res) => { }); await query( - 'INSERT INTO deposits (user_id, amount, stripe_payment_intent_id, status) VALUES (?, ?, ?, ?)', - [req.userId, amount, paymentIntent.id, 'created'] + 'INSERT INTO deposits (user_id, amount, bonus_amount, promo_code_id, stripe_payment_intent_id, status) VALUES (?, ?, ?, ?, ?, ?)', + [req.userId, amount, bonusAmount, promoCodeId, paymentIntent.id, 'created'] ); return res.json({ clientSecret: paymentIntent.client_secret, paymentIntentId: paymentIntent.id }); @@ -72,7 +126,7 @@ router.post('/api/wallet/confirm', authMiddleware, async (req, res) => { } const rows = await query( - 'SELECT id, status, amount FROM deposits WHERE user_id = ? AND stripe_payment_intent_id = ?', + 'SELECT id, status, amount, bonus_amount, promo_code_id FROM deposits WHERE user_id = ? AND stripe_payment_intent_id = ?', [req.userId, paymentIntentId] ); const deposit = rows[0]; @@ -90,7 +144,21 @@ router.post('/api/wallet/confirm', authMiddleware, async (req, res) => { 'UPDATE deposits SET status = ? WHERE id = ?', ['succeeded', deposit.id] ); - await query('UPDATE users SET balance = balance + ? WHERE id = ?', [deposit.amount, req.userId]); + const totalCredit = Number(deposit.amount) + Number(deposit.bonus_amount || 0); + await query('UPDATE users SET balance = balance + ? WHERE id = ?', [totalCredit, req.userId]); + + if (deposit.promo_code_id) { + const already = await query( + 'SELECT id FROM promo_redemptions WHERE user_id = ? AND promo_code_id = ? LIMIT 1', + [req.userId, deposit.promo_code_id] + ); + if (already.length === 0) { + await query( + 'INSERT INTO promo_redemptions (user_id, promo_code_id, deposit_id) VALUES (?, ?, ?)', + [req.userId, deposit.promo_code_id, deposit.id] + ); + } + } } const balanceRows = await query('SELECT balance FROM users WHERE id = ?', [req.userId]);