mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-17 14:21:25 +02:00
fix: admin refund actually sends funds (was calling a nonexistent method)
The admin "send funds" refund path called walletRPCService.sendPayment(),
which does not exist — guarded by a typeof check, so it silently never sent
while still reporting the refund as processed.
Now uses the real processPayout() (validates the address and transfers,
returning { success, txHash }), and refunds the amount the user actually paid
(received_amount, recorded at confirmation) rather than the expected amount,
falling back to expected for older records.
This commit is contained in:
@@ -29,7 +29,7 @@ Key: `S` ≤ half day · `M` ~1–3 days · `L` ~1 week+ · 🔴 launch-blocker
|
||||
- [x] **1.2 S — Snapshot payout config at game start.** ✅ Migration 016 adds `payout_escape_amount`/`payout_treasure_amount` (BIGINT) + multipliers to `games`. Both start paths stamp the resolved payout amounts (via `_computePayoutSnapshot`); `completeGame` pays from the snapshot (falls back to live calc only for pre-snapshot games). New `payoutSnapshot.test.js` proves a mid-game config change can't alter an in-flight payout.
|
||||
- [x] **1.3 S — Close payout uniqueness holes.** ✅ `processing` covered in the unique index (migration 015) and in the in-code existing-payout guard. Migration 017 adds idempotent `NOT VALID` CHECK constraints on `payments.status`, `payments.payment_type`, and `payouts.status` so a typo'd status can't silently orphan a money row (enforces new writes, won't choke on legacy rows). _(games.status/game_mode CHECKs deferred — transient states, lower money-impact.)_
|
||||
- [x] **1.4 S — Fix retry stat guard + non-atomic tx_hash write.** ✅ The "already on-chain" path used a `NOT EXISTS` guard against the row it had just marked completed (always skipped → never counted); replaced with a conditional `UPDATE … WHERE status <> 'completed' RETURNING` so stats count exactly once. Successful retries now store `tx_hash` + mark completed + count stats in ONE transaction (no stranded-tx_hash window). New `payoutRetry.test.js` covers count-once, no-double-count, and atomic completion.
|
||||
- [ ] **1.5 S — Remove dead refund send-path** (`src/index.js:795`).
|
||||
- [x] **1.5 S — Fix dead refund send-path.** ✅ The admin refund called a nonexistent `walletRPCService.sendPayment()` (guarded by a `typeof` check), so refunds were silently never sent while still reporting success. Now uses the real `processPayout()` (validates address + transfers, returns `{success, txHash}`) and refunds the **actually received** amount (`received_amount`, falling back to expected).
|
||||
- [ ] **1.6 S — `credits` → BIGINT; make migration 010 idempotent** (`src/migrations/010_security_constraints.sql`); per-migration transactions in runner (`src/db/databaseManager.js:146`).
|
||||
|
||||
## Phase 2 — Identity, abuse & concurrency (🟠)
|
||||
|
||||
+18
-10
@@ -811,19 +811,27 @@ app.post('/api/admin/refund/payment', adminAuth, asyncHandler(async (req, res) =
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
// Optionally send funds back via wallet RPC
|
||||
// Optionally send funds back via wallet RPC. Refund what the user ACTUALLY paid
|
||||
// (received_amount, recorded at confirmation) when available, falling back to the
|
||||
// expected amount for older records.
|
||||
let fundsSent = false;
|
||||
let txHash = null;
|
||||
if (sendFunds && payment.payout_address && payment.expected_amount > 0) {
|
||||
const refundAmount = (payment.received_amount != null && Number(payment.received_amount) > 0)
|
||||
? payment.received_amount
|
||||
: payment.expected_amount;
|
||||
if (sendFunds && payment.payout_address && Number(refundAmount) > 0) {
|
||||
try {
|
||||
if (walletRPCService && typeof walletRPCService.sendPayment === 'function') {
|
||||
const result = await walletRPCService.sendPayment(
|
||||
payment.payout_address,
|
||||
payment.expected_amount
|
||||
);
|
||||
fundsSent = true;
|
||||
txHash = result?.tx_hash || null;
|
||||
}
|
||||
// processPayout validates the address and performs the transfer; returns
|
||||
// { success, txHash, fee }. (The previous code called a nonexistent sendPayment(),
|
||||
// so refunds were silently never sent while still reporting success.)
|
||||
const result = await walletRPCService.processPayout({
|
||||
address: payment.payout_address,
|
||||
amount: refundAmount,
|
||||
userId: payment.user_id,
|
||||
description: `Refund for payment ${paymentId}`
|
||||
});
|
||||
fundsSent = !!(result && result.success);
|
||||
txHash = (result && result.txHash) || null;
|
||||
} catch (err) {
|
||||
console.error('Failed to send refund funds:', err.message);
|
||||
// Don't fail the request - refund is recorded, funds can be sent manually
|
||||
|
||||
Reference in New Issue
Block a user