mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-21 08:08:32 +02:00
fix multimode payments and get credits working again
This commit is contained in:
+354
-48
@@ -239,6 +239,79 @@ class GameModeManager {
|
||||
return { amount, multiplier, base };
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a confirmed credits package payment - add credits to user
|
||||
* @param {string} socketId - Socket ID of the user
|
||||
* @param {number} paymentId - Payment record ID
|
||||
* @param {object} packageInfo - Package info (credits, bonus, etc.)
|
||||
* @returns {object} Result with success, creditsAdded, newBalance
|
||||
*/
|
||||
async processCreditsPackageConfirmation(socketId, paymentId, packageInfo = null) {
|
||||
try {
|
||||
const user = await this.getOrCreateUser(socketId);
|
||||
|
||||
// Determine credits to add from package or fallback
|
||||
let creditsToAdd = 10; // Default fallback
|
||||
if (packageInfo && packageInfo.credits) {
|
||||
creditsToAdd = Number(packageInfo.credits) + (Number(packageInfo.bonus) || 0);
|
||||
} else {
|
||||
// Try to get from payment record description
|
||||
const paymentResult = await this.db.query(`
|
||||
SELECT description FROM payments WHERE id = $1
|
||||
`, [paymentId]);
|
||||
if (paymentResult.rows.length > 0) {
|
||||
const desc = paymentResult.rows[0].description || '';
|
||||
const match = desc.match(/(\d+)\s*credits?/i);
|
||||
if (match) {
|
||||
creditsToAdd = parseInt(match[1], 10) || 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update user credits
|
||||
const updateResult = await this.db.query(`
|
||||
UPDATE users
|
||||
SET credits = credits + $1,
|
||||
total_credits_purchased = COALESCE(total_credits_purchased, 0) + $1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING credits
|
||||
`, [creditsToAdd, user.id]);
|
||||
|
||||
const newBalance = updateResult.rows[0]?.credits ?? (user.credits + creditsToAdd);
|
||||
|
||||
// Mark payment as processed
|
||||
await this.db.query(`
|
||||
UPDATE payments
|
||||
SET status = 'confirmed',
|
||||
credits_purchased = $1,
|
||||
confirmed_at = NOW()
|
||||
WHERE id = $2
|
||||
`, [creditsToAdd, paymentId]);
|
||||
|
||||
// Record credit transaction
|
||||
await this.db.query(`
|
||||
INSERT INTO credit_transactions (user_id, amount, reason, balance_after, transaction_type)
|
||||
VALUES ($1, $2, 'package_purchase', $3, 'purchase')
|
||||
`, [user.id, creditsToAdd, newBalance]);
|
||||
|
||||
console.log(`💰 Credits package confirmed: +${creditsToAdd} credits for user ${user.id}, new balance: ${newBalance}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
creditsAdded: creditsToAdd,
|
||||
newBalance: newBalance
|
||||
};
|
||||
} catch (error) {
|
||||
const normalized = normalizeError(error, 'Failed to process credits package confirmation');
|
||||
console.error('❌ Error processing credits package:', normalized.message);
|
||||
return {
|
||||
success: false,
|
||||
reason: normalized.safeMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async _findReusablePayment(userId, paymentType) {
|
||||
if (!userId) return null;
|
||||
const result = await this.db.query(`
|
||||
@@ -285,23 +358,193 @@ class GameModeManager {
|
||||
console.log(`⚙️ Payments enabled: ${this.paymentsEnabled}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get effective game mode for a specific user, considering:
|
||||
* - Whether both modes are enabled (mixed mode)
|
||||
* - User's credit balance
|
||||
* - preferCreditsFirst setting
|
||||
* @param {object} user - User object with credits field
|
||||
* @returns {object} { mode, canUseCredits, canUseDirect, hasCredits, creditsBalance }
|
||||
*/
|
||||
getEffectiveModeForUser(user) {
|
||||
const hasCredits = (user?.credits || 0) >= this.creditsPerGameCost;
|
||||
const bothModesEnabled = this.directModeEnabled && this.creditsModeEnabled;
|
||||
|
||||
// Determine available options
|
||||
const canUseCredits = this.creditsModeEnabled && hasCredits;
|
||||
const canUseDirect = this.directModeEnabled;
|
||||
|
||||
// Determine effective mode
|
||||
let effectiveMode;
|
||||
if (!this.paymentsEnabled) {
|
||||
effectiveMode = 'FREE';
|
||||
} else if (bothModesEnabled) {
|
||||
// Mixed mode: prefer based on config and availability
|
||||
if (this.preferCreditsFirst && hasCredits) {
|
||||
effectiveMode = 'PAID_CREDITS';
|
||||
} else if (hasCredits) {
|
||||
effectiveMode = 'PAID_CREDITS'; // Has credits, can use them
|
||||
} else {
|
||||
effectiveMode = 'PAID_SINGLE'; // No credits, must pay
|
||||
}
|
||||
} else if (this.creditsModeEnabled) {
|
||||
effectiveMode = 'PAID_CREDITS';
|
||||
} else if (this.directModeEnabled) {
|
||||
effectiveMode = 'PAID_SINGLE';
|
||||
} else {
|
||||
effectiveMode = 'FREE';
|
||||
}
|
||||
|
||||
return {
|
||||
mode: effectiveMode,
|
||||
canUseCredits,
|
||||
canUseDirect,
|
||||
hasCredits,
|
||||
creditsBalance: user?.credits || 0,
|
||||
creditsRequired: this.creditsPerGameCost,
|
||||
bothModesEnabled,
|
||||
preferCreditsFirst: this.preferCreditsFirst,
|
||||
creditsPayoutsEnabled: this.creditsPayoutEnabled,
|
||||
directPayoutsEnabled: this.directPayoutMultipliers.escape > 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available payment options for a user
|
||||
* @param {string} socketId - Socket ID
|
||||
* @returns {object} Available options for the user
|
||||
*/
|
||||
async getPaymentOptionsForUser(socketId) {
|
||||
try {
|
||||
const user = await this.getOrCreateUser(socketId);
|
||||
const effective = this.getEffectiveModeForUser(user);
|
||||
|
||||
const options = [];
|
||||
|
||||
if (effective.canUseCredits) {
|
||||
options.push({
|
||||
type: 'use_credit',
|
||||
label: `Use 1 Credit (${effective.creditsBalance} available)`,
|
||||
mode: 'PAID_CREDITS',
|
||||
cost: 0,
|
||||
costDisplay: '1 credit',
|
||||
payoutEligible: effective.creditsPayoutsEnabled,
|
||||
recommended: effective.preferCreditsFirst
|
||||
});
|
||||
}
|
||||
|
||||
if (effective.canUseDirect) {
|
||||
options.push({
|
||||
type: 'pay_direct',
|
||||
label: `Pay ${this.formatAtomicHuman(this.singleGamePrice, 2)} ${this.cryptoType}`,
|
||||
mode: 'PAID_SINGLE',
|
||||
cost: this.singleGamePrice,
|
||||
costDisplay: `${this.formatAtomicHuman(this.singleGamePrice, 2)} ${this.cryptoType}`,
|
||||
payoutEligible: effective.directPayoutsEnabled,
|
||||
recommended: !effective.preferCreditsFirst || !effective.hasCredits
|
||||
});
|
||||
}
|
||||
|
||||
if (this.creditsModeEnabled) {
|
||||
const pkg = this.getPrimaryCreditPackage();
|
||||
options.push({
|
||||
type: 'buy_credits',
|
||||
label: `Buy ${pkg.credits} Credits`,
|
||||
mode: 'PURCHASE',
|
||||
cost: Number(pkg.price),
|
||||
costDisplay: `${this.formatAtomicHuman(pkg.price, 2)} ${this.cryptoType}`,
|
||||
credits: pkg.credits + (pkg.bonus || 0),
|
||||
payoutEligible: false,
|
||||
recommended: false
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
user: {
|
||||
credits: effective.creditsBalance,
|
||||
hasPayoutAddress: !!user.payout_address
|
||||
},
|
||||
effective,
|
||||
options
|
||||
};
|
||||
} catch (error) {
|
||||
const normalized = normalizeError(error, 'Failed to get payment options');
|
||||
console.error('❌ Error getting payment options:', normalized.message);
|
||||
return {
|
||||
user: { credits: 0, hasPayoutAddress: false },
|
||||
effective: { mode: 'FREE' },
|
||||
options: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can start a game
|
||||
*/
|
||||
async canUserStartGame(socketId) {
|
||||
try {
|
||||
const user = await this.getOrCreateUser(socketId);
|
||||
const effective = this.getEffectiveModeForUser(user);
|
||||
|
||||
switch (this.gameMode) {
|
||||
case 'FREE':
|
||||
return { allowed: true, reason: 'Free mode' };
|
||||
|
||||
// FREE mode or payments disabled
|
||||
if (effective.mode === 'FREE') {
|
||||
return { allowed: true, reason: 'Free mode' };
|
||||
}
|
||||
|
||||
// Mixed mode: check if user can play with credits OR has confirmed payment
|
||||
if (effective.bothModesEnabled) {
|
||||
// Option 1: User has credits
|
||||
if (effective.hasCredits) {
|
||||
return {
|
||||
allowed: true,
|
||||
reason: `${effective.creditsBalance} credits available`,
|
||||
useCredits: true,
|
||||
creditsRequired: this.creditsPerGameCost,
|
||||
balance: effective.creditsBalance,
|
||||
effectiveMode: 'PAID_CREDITS'
|
||||
};
|
||||
}
|
||||
|
||||
// Option 2: User has confirmed single_game payment
|
||||
const pendingPayment = await this.db.query(`
|
||||
SELECT * FROM payments
|
||||
WHERE socket_id = $1 AND status = 'confirmed'
|
||||
AND payment_type = 'single_game'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM games
|
||||
WHERE games.payment_id = payments.id
|
||||
)
|
||||
ORDER BY confirmed_at DESC
|
||||
LIMIT 1
|
||||
`, [socketId]);
|
||||
|
||||
if (pendingPayment.rows.length > 0) {
|
||||
return {
|
||||
allowed: true,
|
||||
reason: 'Payment confirmed',
|
||||
paymentId: pendingPayment.rows[0].id,
|
||||
effectiveMode: 'PAID_SINGLE'
|
||||
};
|
||||
}
|
||||
|
||||
// Neither credits nor payment available
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'Payment or credits required',
|
||||
action: 'choose_payment',
|
||||
options: await this.getPaymentOptionsForUser(socketId)
|
||||
};
|
||||
}
|
||||
|
||||
// Single mode logic (backwards compatible)
|
||||
switch (effective.mode) {
|
||||
case 'PAID_CREDITS':
|
||||
if (user.credits >= this.creditsPerGameCost) {
|
||||
if (effective.hasCredits) {
|
||||
return {
|
||||
allowed: true,
|
||||
reason: `${user.credits} credits remaining`,
|
||||
creditsRequired: this.creditsPerGameCost
|
||||
reason: `${effective.creditsBalance} credits remaining`,
|
||||
creditsRequired: this.creditsPerGameCost,
|
||||
effectiveMode: 'PAID_CREDITS'
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -309,12 +552,11 @@ class GameModeManager {
|
||||
reason: 'Insufficient credits',
|
||||
action: 'purchase_credits',
|
||||
creditsRequired: this.creditsPerGameCost,
|
||||
balance: user.credits
|
||||
balance: effective.creditsBalance
|
||||
};
|
||||
|
||||
case 'PAID_SINGLE':
|
||||
// Check for pending payment
|
||||
const pendingPayment = await this.db.query(`
|
||||
const payment = await this.db.query(`
|
||||
SELECT * FROM payments
|
||||
WHERE socket_id = $1 AND status = 'confirmed'
|
||||
AND payment_type = 'single_game'
|
||||
@@ -326,11 +568,12 @@ class GameModeManager {
|
||||
LIMIT 1
|
||||
`, [socketId]);
|
||||
|
||||
if (pendingPayment.rows.length > 0) {
|
||||
if (payment.rows.length > 0) {
|
||||
return {
|
||||
allowed: true,
|
||||
reason: 'Payment confirmed',
|
||||
paymentId: pendingPayment.rows[0].id
|
||||
paymentId: payment.rows[0].id,
|
||||
effectiveMode: 'PAID_SINGLE'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -356,31 +599,51 @@ class GameModeManager {
|
||||
async processGameStart(socketId, gameId) {
|
||||
try {
|
||||
const user = await this.getOrCreateUser(socketId);
|
||||
const effective = this.getEffectiveModeForUser(user);
|
||||
|
||||
switch (this.gameMode) {
|
||||
case 'FREE':
|
||||
// No processing needed for free mode
|
||||
return { success: true };
|
||||
|
||||
// FREE mode
|
||||
if (effective.mode === 'FREE') {
|
||||
return { success: true, effectiveMode: 'FREE' };
|
||||
}
|
||||
|
||||
// Mixed mode: determine which method to use
|
||||
if (effective.bothModesEnabled) {
|
||||
// Prefer credits if available and preferCreditsFirst is true
|
||||
if (effective.hasCredits && this.preferCreditsFirst) {
|
||||
return await this._processGameStartWithCredits(user, socketId, gameId);
|
||||
}
|
||||
|
||||
// Check for confirmed direct payment
|
||||
const payment = await this.db.query(`
|
||||
SELECT * FROM payments
|
||||
WHERE socket_id = $1 AND status = 'confirmed'
|
||||
AND payment_type = 'single_game'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM games
|
||||
WHERE games.payment_id = payments.id
|
||||
)
|
||||
ORDER BY confirmed_at DESC
|
||||
LIMIT 1
|
||||
`, [socketId]);
|
||||
|
||||
if (payment.rows.length > 0) {
|
||||
return await this._processGameStartWithPayment(user, payment.rows[0], gameId);
|
||||
}
|
||||
|
||||
// Fall back to credits if available
|
||||
if (effective.hasCredits) {
|
||||
return await this._processGameStartWithCredits(user, socketId, gameId);
|
||||
}
|
||||
|
||||
return { success: false, reason: 'No valid payment or credits found' };
|
||||
}
|
||||
|
||||
// Single mode logic
|
||||
switch (effective.mode) {
|
||||
case 'PAID_CREDITS':
|
||||
// Deduct required credits for this mode
|
||||
const creditsToSpend = this.creditsPerGameCost;
|
||||
const updateRes = await this.db.query(`
|
||||
UPDATE users
|
||||
SET credits = credits - $1,
|
||||
total_games_played = total_games_played + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING credits
|
||||
`, [creditsToSpend, user.id]);
|
||||
const remainingCredits = updateRes.rows[0] ? updateRes.rows[0].credits : (user.credits - creditsToSpend);
|
||||
// Emit credits update asynchronously if an io ref was injected later (pattern: attach externally)
|
||||
try { this.io && this.io.to(socketId).emit('credits_update', { balance: remainingCredits }); } catch(_) {}
|
||||
console.log(`🎫 Deducted ${creditsToSpend} credit(s) from user ${user.id}, ${remainingCredits} remaining`);
|
||||
return { success: true, creditsRemaining: remainingCredits, creditsSpent: creditsToSpend };
|
||||
return await this._processGameStartWithCredits(user, socketId, gameId);
|
||||
|
||||
case 'PAID_SINGLE':
|
||||
// Link game to payment
|
||||
const payment = await this.db.query(`
|
||||
SELECT * FROM payments
|
||||
WHERE socket_id = $1 AND status = 'confirmed'
|
||||
@@ -397,21 +660,7 @@ class GameModeManager {
|
||||
return { success: false, reason: 'No valid payment found' };
|
||||
}
|
||||
|
||||
await this.db.query(`
|
||||
UPDATE games
|
||||
SET payment_id = $1
|
||||
WHERE id = $2
|
||||
`, [payment.rows[0].id, gameId]);
|
||||
|
||||
await this.db.query(`
|
||||
UPDATE users
|
||||
SET total_games_played = total_games_played + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`, [user.id]);
|
||||
|
||||
console.log(`💳 Linked game ${gameId} to payment ${payment.rows[0].id}`);
|
||||
return { success: true, paymentId: payment.rows[0].id };
|
||||
return await this._processGameStartWithPayment(user, payment.rows[0], gameId);
|
||||
|
||||
default:
|
||||
return { success: false, reason: 'Invalid game mode' };
|
||||
@@ -423,6 +672,63 @@ class GameModeManager {
|
||||
}
|
||||
}
|
||||
|
||||
async _processGameStartWithCredits(user, socketId, gameId) {
|
||||
const creditsToSpend = this.creditsPerGameCost;
|
||||
const updateRes = await this.db.query(`
|
||||
UPDATE users
|
||||
SET credits = credits - $1,
|
||||
total_games_played = total_games_played + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING credits
|
||||
`, [creditsToSpend, user.id]);
|
||||
const remainingCredits = updateRes.rows[0] ? updateRes.rows[0].credits : (user.credits - creditsToSpend);
|
||||
|
||||
// Record the game mode used
|
||||
await this.db.query(`
|
||||
UPDATE games SET payment_mode = 'credits' WHERE id = $1
|
||||
`, [gameId]);
|
||||
|
||||
// Record credit transaction
|
||||
await this.db.query(`
|
||||
INSERT INTO credit_transactions (user_id, amount, reason, balance_after, transaction_type)
|
||||
VALUES ($1, $2, 'game_entry', $3, 'spend')
|
||||
`, [user.id, -creditsToSpend, remainingCredits]);
|
||||
|
||||
// Emit credits update
|
||||
try { this.io && this.io.to(socketId).emit('credits_update', { balance: remainingCredits }); } catch(_) {}
|
||||
console.log(`🎫 Deducted ${creditsToSpend} credit(s) from user ${user.id}, ${remainingCredits} remaining`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
creditsRemaining: remainingCredits,
|
||||
creditsSpent: creditsToSpend,
|
||||
effectiveMode: 'PAID_CREDITS'
|
||||
};
|
||||
}
|
||||
|
||||
async _processGameStartWithPayment(user, payment, gameId) {
|
||||
await this.db.query(`
|
||||
UPDATE games
|
||||
SET payment_id = $1, payment_mode = 'direct'
|
||||
WHERE id = $2
|
||||
`, [payment.id, gameId]);
|
||||
|
||||
await this.db.query(`
|
||||
UPDATE users
|
||||
SET total_games_played = total_games_played + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`, [user.id]);
|
||||
|
||||
console.log(`💳 Linked game ${gameId} to payment ${payment.id}`);
|
||||
return {
|
||||
success: true,
|
||||
paymentId: payment.id,
|
||||
effectiveMode: 'PAID_SINGLE'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Process game completion (handle payouts)
|
||||
*/
|
||||
|
||||
@@ -200,6 +200,8 @@ app.get('/health', (req, res) => {
|
||||
memoryUsage: process.memoryUsage(),
|
||||
gameMode: gameModeManager.gameMode,
|
||||
paymentsEnabled: gameModeManager.paymentsEnabled,
|
||||
directModeEnabled: gameModeManager.directModeEnabled,
|
||||
creditsModeEnabled: gameModeManager.creditsModeEnabled,
|
||||
walletHealthy: walletRPCService.isHealthy,
|
||||
activeGames: activeGames.size,
|
||||
debugMode: debugManager.getDebugStatus().debugMode
|
||||
@@ -207,6 +209,27 @@ app.get('/health', (req, res) => {
|
||||
res.json(health);
|
||||
});
|
||||
|
||||
// Get payment options for a user (mixed mode support)
|
||||
app.get('/api/user/:socketId/payment-options', asyncHandler(async (req, res) => {
|
||||
const { socketId } = req.params;
|
||||
if (!socketId) {
|
||||
throw new ValidationError('Missing socketId', {
|
||||
safeMessage: 'socketId parameter is required.'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const options = await gameModeManager.getPaymentOptionsForUser(socketId);
|
||||
res.json(options);
|
||||
} catch (error) {
|
||||
throw new AppError('Failed to retrieve payment options', {
|
||||
statusCode: 500,
|
||||
safeMessage: 'Unable to retrieve payment options.',
|
||||
cause: error
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
app.get('/api/game-modes', (req, res) => {
|
||||
const config = paymentConfigManager.getConfig();
|
||||
const decimals = Number(config.currency?.decimals ?? 12);
|
||||
|
||||
@@ -153,7 +153,7 @@ class PaymentHandlers {
|
||||
|
||||
// If we reused an existing request, ensure we refresh monitoring to avoid duplicate watchers
|
||||
this.stopMonitoringForSocket(socket.id);
|
||||
this._monitorAddress(socket, paymentRequest, paymentRequest.amount, cryptoType, currentUser);
|
||||
this._monitorAddress(socket, paymentRequest, paymentRequest.amount, cryptoType, currentUser, paymentType);
|
||||
} catch (e) {
|
||||
const err = normalizeError(e, 'Failed to create payment request');
|
||||
console.error('Error creating payment request:', err.message);
|
||||
@@ -161,13 +161,15 @@ class PaymentHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
_monitorAddress(socket, paymentRequest, amount, cryptoType, currentUser) {
|
||||
_monitorAddress(socket, paymentRequest, amount, cryptoType, currentUser, paymentType = 'single_game') {
|
||||
// Record mapping so we can stop later (replace existing entry)
|
||||
this.socketPaymentMap.set(socket.id, {
|
||||
address: paymentRequest.address,
|
||||
paymentId: paymentRequest.id,
|
||||
amount: paymentRequest.amount,
|
||||
cryptoType,
|
||||
paymentType,
|
||||
package: paymentRequest.package,
|
||||
createdAt: Date.now()
|
||||
});
|
||||
|
||||
@@ -184,15 +186,47 @@ class PaymentHandlers {
|
||||
} else if (status.confirmed) {
|
||||
if (!this.confirmedPayments.has(paymentRequest.id)) {
|
||||
this.confirmedPayments.add(paymentRequest.id);
|
||||
socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed in block!', confirmations: status.confirmations });
|
||||
this._confirmedTimestamps.set(paymentRequest.id, Date.now());
|
||||
this.queueManager.markConfirmed(socket.id);
|
||||
// Attempt immediate game start so user doesn't wait another full block
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null;
|
||||
if (currentBlock !== null) {
|
||||
const started = this.queueManager.startGameImmediately(socket.id, currentBlock);
|
||||
if (!started && this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`[PaymentHandlers] Immediate start skipped (not queued or still unconfirmed) for ${socket.id}`);
|
||||
|
||||
// Handle credits_package: add credits to user
|
||||
const mapping = this.socketPaymentMap.get(socket.id);
|
||||
if (mapping && mapping.paymentType === 'credits_package' && this.gameModeManager) {
|
||||
try {
|
||||
const creditsResult = await this.gameModeManager.processCreditsPackageConfirmation(
|
||||
socket.id,
|
||||
paymentRequest.id,
|
||||
mapping.package
|
||||
);
|
||||
if (creditsResult.success) {
|
||||
socket.emit('credits_update', { balance: creditsResult.newBalance });
|
||||
socket.emit('payment_confirmed', {
|
||||
paymentId: paymentRequest.id,
|
||||
message: `Payment confirmed! Added ${creditsResult.creditsAdded} credits. New balance: ${creditsResult.newBalance}`,
|
||||
creditsAdded: creditsResult.creditsAdded,
|
||||
newBalance: creditsResult.newBalance,
|
||||
confirmations: status.confirmations
|
||||
});
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'success',
|
||||
`✅ CREDITS PURCHASED!\n\n+${creditsResult.creditsAdded} credits added.\nNew balance: ${creditsResult.newBalance} credits.\n\nType 'enter' to start a game!`);
|
||||
} else {
|
||||
console.error('Failed to process credits package:', creditsResult.reason);
|
||||
socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed but failed to add credits. Contact support.', confirmations: status.confirmations });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error processing credits package confirmation:', e.message);
|
||||
socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed in block!', confirmations: status.confirmations });
|
||||
}
|
||||
} else {
|
||||
// single_game payment - standard flow
|
||||
socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed in block!', confirmations: status.confirmations });
|
||||
this.queueManager.markConfirmed(socket.id);
|
||||
// Attempt immediate game start so user doesn't wait another full block
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null;
|
||||
if (currentBlock !== null) {
|
||||
const started = this.queueManager.startGameImmediately(socket.id, currentBlock);
|
||||
if (!started && this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`[PaymentHandlers] Immediate start skipped (not queued or still unconfirmed) for ${socket.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,24 +106,23 @@ class MoneroPayService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process payment confirmation callback
|
||||
* Process payment callback (when payment confirmed)
|
||||
*/
|
||||
async processPaymentCallback(socketId, paymentData) {
|
||||
async processPaymentCallback(paymentData) {
|
||||
try {
|
||||
const payment = await this.db.query(`
|
||||
// Find the payment record
|
||||
const paymentResult = await this.db.query(`
|
||||
SELECT * FROM payments
|
||||
WHERE socket_id = $1 AND status = 'pending'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`, [socketId]);
|
||||
WHERE subaddress = $1 AND status = 'pending'
|
||||
`, [paymentData.address]);
|
||||
|
||||
if (payment.rows.length === 0) {
|
||||
console.warn(`⚠️ No pending payment found for socket ${socketId}`);
|
||||
if (paymentResult.rows.length === 0) {
|
||||
console.warn('⚠️ Payment callback for unknown address:', paymentData.address);
|
||||
return false;
|
||||
}
|
||||
|
||||
const paymentRecord = payment.rows[0];
|
||||
|
||||
const paymentRecord = paymentResult.rows[0];
|
||||
|
||||
// Update payment status
|
||||
await this.db.query(`
|
||||
UPDATE payments
|
||||
@@ -144,16 +143,23 @@ class MoneroPayService {
|
||||
|
||||
// Handle different payment types
|
||||
if (paymentRecord.payment_type === 'credits_package') {
|
||||
// Add 10 credits to user
|
||||
// Parse credits from description or use default
|
||||
// Description format: "Wowngeon X credits package (WOW)"
|
||||
let creditsToAdd = 10; // Default fallback
|
||||
const descMatch = paymentRecord.description?.match(/(\d+)\s*credits/i);
|
||||
if (descMatch) {
|
||||
creditsToAdd = parseInt(descMatch[1], 10) || 10;
|
||||
}
|
||||
|
||||
await this.db.query(`
|
||||
UPDATE users
|
||||
SET credits = credits + 10,
|
||||
total_amount_paid = total_amount_paid + $1,
|
||||
SET credits = credits + $1,
|
||||
total_amount_paid = total_amount_paid + $2,
|
||||
updated_at = NOW()
|
||||
WHERE id = $2
|
||||
`, [paymentData.amount, paymentRecord.user_id]);
|
||||
WHERE id = $3
|
||||
`, [creditsToAdd, paymentData.amount, paymentRecord.user_id]);
|
||||
|
||||
console.log(`💰 Added 10 credits to user ${paymentRecord.user_id}`);
|
||||
console.log(`💰 Added ${creditsToAdd} credits to user ${paymentRecord.user_id}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Payment confirmed: ${paymentData.tx_hash} for ${paymentData.amount} atomic units`);
|
||||
|
||||
Reference in New Issue
Block a user