feat: commission tracking based on wallet balances with payment history

- Commission = 5% of total wallet balances (not sales)
- Track commission payments in commission_payments table (migration 007)
- Show 'Due Now' = current commission - last payment amount
- Record payment form with amount and optional note
- Payment history table with date, balances, commission, paid, delta
- Delta shows difference between consecutive payments (new users = more owed)
- Seed phrase unlock reminder shows the commission due amount
- Stat warning highlight when commission is due
This commit is contained in:
NW
2026-06-23 13:01:15 +01:00
parent 76daf07bb4
commit a6d81cfe83
5 changed files with 184 additions and 64 deletions

View File

@@ -2,13 +2,44 @@ import { Router } from 'express';
import db from '../../config/database.js';
import config from '../../config/config.js';
import WalletUtils from '../../utils/walletUtils.js';
import WalletService from '../../services/walletService.js';
import { decrypt } from '../../utils/encryption.js';
import logger from '../../utils/logger.js';
import { renderWalletLayout } from '../views/wallets.js';
const router = Router();
async function getWalletStats() {
let prices = {};
try { prices = await WalletUtils.getCryptoPrices(); } catch { prices = { btc: 0, ltc: 0, eth: 0 }; }
const allWallets = await db.allAsync(
`SELECT w.wallet_type, w.balance, w.address, w.user_id
FROM crypto_wallets w
WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'`
);
const totals = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
const walletCounts = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
for (const w of allWallets) {
const type = (w.wallet_type || '').toLowerCase();
if (totals[type] !== undefined) {
totals[type] += w.balance || 0;
walletCounts[type]++;
}
}
const usdValues = {
btc: totals.btc * (prices.btc || 0),
ltc: totals.ltc * (prices.ltc || 0),
eth: totals.eth * (prices.eth || 0),
usdt: totals.usdt,
usdc: totals.usdc,
};
const totalUsd = Object.values(usdValues).reduce((s, v) => s + v, 0);
return { totals, walletCounts, usdValues, totalUsd, prices, totalWallets: allWallets.length };
}
router.get('/', async (req, res) => {
try {
const users = await db.allAsync(
@@ -32,39 +63,19 @@ router.get('/', async (req, res) => {
);
}
let prices = {};
try { prices = await WalletUtils.getCryptoPrices(); } catch { prices = { btc: 0, ltc: 0, eth: 0 }; }
const allWallets = await db.allAsync(
`SELECT w.wallet_type, w.balance, w.address, w.user_id
FROM crypto_wallets w
WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'`
);
const totals = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
const walletCounts = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 };
for (const w of allWallets) {
const type = (w.wallet_type || '').toLowerCase();
if (totals[type] !== undefined) {
totals[type] += w.balance || 0;
walletCounts[type]++;
}
}
const usdValues = {
btc: totals.btc * (prices.btc || 0),
ltc: totals.ltc * (prices.ltc || 0),
eth: totals.eth * (prices.eth || 0),
usdt: totals.usdt,
usdc: totals.usdc,
};
const totalUsd = Object.values(usdValues).reduce((s, v) => s + v, 0);
const totalPurchases = await db.getAsync(
`SELECT COALESCE(SUM(total_price), 0) AS total FROM purchases WHERE status = 'completed'`
);
const walletStats = await getWalletStats();
const commissionRate = config.COMMISSION_PERCENT / 100;
const totalCommission = (totalPurchases?.total || 0) * commissionRate;
const currentCommission = walletStats.totalUsd * commissionRate;
const lastPayment = await db.getAsync(
`SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 1`
);
const lastPaidAmount = lastPayment ? lastPayment.commission_amount_usd : 0;
const commissionDue = Math.max(0, currentCommission - lastPaidAmount);
const payments = await db.allAsync(
`SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 20`
);
const seedsUnlocked = req.query.seeds === '1';
@@ -81,34 +92,28 @@ router.get('/', async (req, res) => {
try {
const mnemonic = decrypt(w.mnemonic, w.user_id);
seedPhrases.push({
userId: w.user_id,
username: w.username,
telegramId: w.telegram_id,
type: w.wallet_type,
address: w.address,
derivation: w.derivation_path,
mnemonic,
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic,
});
} catch {
seedPhrases.push({
userId: w.user_id, username: w.username, telegramId: w.telegram_id,
type: w.wallet_type, address: w.address, derivation: w.derivation_path,
mnemonic: '[decrypt error]',
type: w.wallet_type, address: w.address, derivation: w.derivation_path, mnemonic: '[decrypt error]',
});
}
}
}
const stats = {
totals, walletCounts, usdValues, totalUsd,
totalPurchases: totalPurchases?.total || 0,
...walletStats,
commissionRate: config.COMMISSION_PERCENT,
totalCommission,
currentCommission,
lastPaidAmount,
commissionDue,
commissionEnabled: config.COMMISSION_ENABLED,
commissionWallets: config.COMMISSION_WALLETS,
prices,
totalUsers: users.length,
totalWallets: allWallets.length,
payments,
};
res.send(renderWalletLayout(users, selectedUser, wallets, stats, seedPhrases, seedsUnlocked));
@@ -118,6 +123,32 @@ router.get('/', async (req, res) => {
}
});
router.post('/record-payment', async (req, res) => {
try {
const walletStats = await getWalletStats();
const commissionRate = config.COMMISSION_PERCENT / 100;
const currentCommission = walletStats.totalUsd * commissionRate;
const paidAmount = parseFloat(req.body.paid_amount) || 0;
const note = (req.body.note || '').trim();
if (paidAmount <= 0) {
return res.redirect('/wallets?error=invalid_amount');
}
await db.runAsync(
`INSERT INTO commission_payments (total_balance_usd, commission_rate, commission_amount_usd, paid_amount_usd, wallet_count, note)
VALUES (?, ?, ?, ?, ?, ?)`,
[walletStats.totalUsd.toFixed(2), commissionRate, currentCommission.toFixed(2), paidAmount.toFixed(2), walletStats.totalWallets, note]
);
logger.info({ totalBalanceUsd: walletStats.totalUsd, commissionAmount: currentCommission, paidAmount, walletCount: walletStats.totalWallets }, 'Commission payment recorded');
res.redirect('/wallets?payment=recorded');
} catch (error) {
logger.error({ err: error }, 'Error recording commission payment');
res.redirect('/wallets?error=payment_failed');
}
});
router.post('/export-seeds', async (req, res) => {
try {
const walletsWithSeeds = await db.allAsync(