fix credits logic

This commit is contained in:
jwinterm
2026-02-20 09:21:13 -05:00
parent 5776c58357
commit ee5a037b84
4 changed files with 74 additions and 35 deletions
+27 -11
View File
@@ -870,18 +870,34 @@ const SocketHandlers = {
// Hide the QR code now that payment is confirmed
SocketHandlers.hidePaymentQR();
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
// Credits purchase: return to home screen (no game queue)
if (data.creditsAdded || data.newBalance !== undefined) {
if (typeof Game !== 'undefined') {
Game._pendingPaymentConfirmed();
}
// Return to welcome/home screen so user can start a game
if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWelcomeScreen) {
ScreenManager.drawWelcomeScreen();
}
$('#messages').append($('<li class="payment-success" style="color:#0f0;">').html(
'✅ <strong>Credits purchased!</strong> Type \'enter\' or click START GAME to play.'
));
UI.scrollChat();
SocketHandlers._setBannerStatus('Credits Added', '#0f0');
} else {
// Single game payment: show waiting screen (player is in queue)
if (typeof Game !== 'undefined' && Game.drawWaitingScreen) {
Game.drawWaitingScreen();
}
if (typeof Game !== 'undefined') {
Game._pendingPaymentConfirmed();
}
$('#messages').append($('<li class="payment-success" style="color:#0f0;">').html(
'✅ <strong>Payment confirmed in block.</strong> You are in the game queue.'
));
UI.scrollChat();
SocketHandlers._setBannerStatus('Confirmed', '#0f0');
}
if (typeof Game !== 'undefined') {
Game._pendingPaymentConfirmed();
}
$('#messages').append($('<li class="payment-success" style="color:#0f0;">').html(
'✅ <strong>Payment confirmed in block.</strong> You are in the game queue.'
));
UI.scrollChat();
SocketHandlers._setBannerStatus('Confirmed', '#0f0');
// Sound handled by AudioAlerts._patchSocketHandlers() - don't duplicate
},
+15 -6
View File
@@ -638,15 +638,24 @@ const adminAuth = (req, res, next) => {
}
// Origin check — reject cross-origin requests to admin endpoints
// Same-origin requests (Origin matches Host) are always allowed
const origin = req.headers.origin || req.headers.referer;
if (origin) {
try {
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
: [`http://localhost:${process.env.PORT || 3000}`, `http://127.0.0.1:${process.env.PORT || 3000}`];
const requestOrigin = new URL(origin).origin;
if (!allowedOrigins.includes(requestOrigin)) {
return res.status(403).json({ error: 'Forbidden', message: 'Origin not allowed.' });
const host = req.headers.host;
const protocol = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
const selfOrigin = `${protocol}://${host}`;
// Allow same-origin requests (admin.html served from same server)
if (requestOrigin !== selfOrigin) {
// Check explicit allowlist for cross-origin requests
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim())
: [`http://localhost:${process.env.PORT || 3000}`, `http://127.0.0.1:${process.env.PORT || 3000}`];
if (!allowedOrigins.includes(requestOrigin)) {
return res.status(403).json({ error: 'Forbidden', message: 'Origin not allowed.' });
}
}
} catch (e) {
return res.status(403).json({ error: 'Forbidden', message: 'Invalid origin.' });
@@ -1804,7 +1813,7 @@ async function startServer() {
const payoutIntervalMs = payoutIntervalSeconds * 1000;
batchPayoutInterval = setInterval(async () => {
try {
await walletRPCService.processBatchPayouts();
await gameModeManager._processPendingPayouts();
} catch (error) {
console.error('Error in batch payout processing:', error);
}
+31 -17
View File
@@ -279,25 +279,37 @@ class PaymentHandlers {
if (status.in_mempool && !status.confirmed) {
if (this.mempoolNotified.has(paymentRequest.address)) return;
this.mempoolNotified.add(paymentRequest.address);
socket.emit('payment_detected', { paymentId: paymentRequest.id, message: 'Payment detected in mempool! Adding you to the game queue...', amount: status.amount, confirmations: 0 });
const existingIdx = this.queueManager.getPlayerIndex(socket.id);
if (existingIdx === -1) {
// Try to get DB userId from session
let userId = null;
if (this.sessionManager?.sessions?.has(socket.id)) {
userId = this.sessionManager.sessions.get(socket.id).id;
}
this.queueManager.addPlayer({
serverId: socket.id,
clientId: currentUser ? currentUser.clientId : null,
userId: userId,
paymentId: paymentRequest.id,
requiresConfirmation: true,
confirmed: false
});
// Check payment type — credits_package should NOT queue for a game
const mapping = this.socketPaymentMap.get(socket.id);
const isCredits = mapping && mapping.paymentType === 'credits_package';
const detectMessage = isCredits
? 'Payment detected in mempool! Awaiting block confirmation to add credits...'
: 'Payment detected in mempool! Adding you to the game queue...';
socket.emit('payment_detected', { paymentId: paymentRequest.id, message: detectMessage, amount: status.amount, confirmations: 0 });
// Only queue for single_game payments (credits just add balance, no game queue)
if (!isCredits) {
const existingIdx = this.queueManager.getPlayerIndex(socket.id);
if (existingIdx === -1) {
// Try to get DB userId from session
let userId = null;
if (this.sessionManager?.sessions?.has(socket.id)) {
userId = this.sessionManager.sessions.get(socket.id).id;
}
this.queueManager.addPlayer({
serverId: socket.id,
clientId: currentUser ? currentUser.clientId : null,
userId: userId,
paymentId: paymentRequest.id,
requiresConfirmation: true,
confirmed: false
});
}
socket.emit('queue_joined', { position: (existingIdx === -1 ? this.queueManager.getQueueLength() : existingIdx + 1), message: 'Payment received! Waiting for next block to start game...', currentBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null, nextBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() + 1 : null });
}
socket.emit('queue_joined', { position: (existingIdx === -1 ? this.queueManager.getQueueLength() : existingIdx + 1), message: 'Payment received! Waiting for next block to start game...', currentBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null, nextBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() + 1 : null });
} else if (status.confirmed) {
// SECURITY: Use in-memory Set as fast-path, but DB is source of truth
// This prevents double-processing on server restart
@@ -344,6 +356,8 @@ class PaymentHandlers {
console.error('Error processing credits package confirmation:', e.message);
socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed in block!', confirmations: status.confirmations });
}
// Defensively remove from queue — credits purchases should never be queued
this.queueManager.removePlayer(socket.id);
} else {
// single_game payment - standard flow
// CRITICAL: Atomically update payment status and check if we actually updated it
+1 -1
View File
@@ -537,7 +537,7 @@ class SocketHandlers {
// Create game immediately
const blockHeight = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null;
memUser.blockRec = blockHeight; // keep legacy timeout logic consistent
const game = this.gameManager.createGameForUser(memUser, 'standard');
const game = await this.gameManager.createGameForUser(memUser, 'standard');
const state = game.getState();
state.blockHeight = blockHeight;