Add promo code functionality to deposits and enhance wallet routes

This commit is contained in:
2025-12-21 00:41:39 +01:00
parent 3861b4477d
commit 97ed3c4d9e
2 changed files with 100 additions and 5 deletions

View File

@@ -8,15 +8,42 @@ CREATE TABLE IF NOT EXISTS users (
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 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 ( CREATE TABLE IF NOT EXISTS deposits (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL,
amount INT 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, stripe_payment_intent_id VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'created', status VARCHAR(32) NOT NULL DEFAULT 'created',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 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 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)
);

View File

@@ -19,6 +19,27 @@ function ensureStripeConfigured(res) {
return true; 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) => { router.post('/api/wallet/deposit-intent', authMiddleware, async (req, res) => {
try { try {
if (!ensureStripeConfigured(res)) { 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.` }); 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({ const paymentIntent = await stripe.paymentIntents.create({
amount: stripeAmount, amount: stripeAmount,
currency: 'huf', currency: 'huf',
@@ -46,8 +100,8 @@ router.post('/api/wallet/deposit-intent', authMiddleware, async (req, res) => {
}); });
await query( await query(
'INSERT INTO deposits (user_id, amount, stripe_payment_intent_id, status) VALUES (?, ?, ?, ?)', 'INSERT INTO deposits (user_id, amount, bonus_amount, promo_code_id, stripe_payment_intent_id, status) VALUES (?, ?, ?, ?, ?, ?)',
[req.userId, amount, paymentIntent.id, 'created'] [req.userId, amount, bonusAmount, promoCodeId, paymentIntent.id, 'created']
); );
return res.json({ clientSecret: paymentIntent.client_secret, paymentIntentId: paymentIntent.id }); 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( 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] [req.userId, paymentIntentId]
); );
const deposit = rows[0]; const deposit = rows[0];
@@ -90,7 +144,21 @@ router.post('/api/wallet/confirm', authMiddleware, async (req, res) => {
'UPDATE deposits SET status = ? WHERE id = ?', 'UPDATE deposits SET status = ? WHERE id = ?',
['succeeded', deposit.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]); const balanceRows = await query('SELECT balance FROM users WHERE id = ?', [req.userId]);