Refunds for Money Never Taken — JavaScript Bug Hunt
Modelled on Revolut's US payments flaw (2022, reported by the Financial Times in 2023): a problem in how Revolut's US card system handled certain declined…
- Language: JavaScript
- Layer: Database
- Difficulty: Easy
- Concepts: Money, Validation, State
- Modelled on: Revolut · 2022
- Visible tests: a settled charge is refunded; a declined charge is never refunded
- Reward: 50 XP for a complete fix
Briefing
Modelled on Revolut's US payments flaw (2022, reported by the Financial Times in 2023): a problem in how Revolut's US card system handled certain declined transactions meant refunds were issued from Revolut's own funds for payments whose money had never actually left the customer's account. Criminals exploited it before it was closed; the reported losses were around $20 million.
reversal.js credits a customer when the card network reports a transaction as failed or reversed. It credits every reversal it is told about.
Fix reverse so a refund only ever returns money that was actually taken, once.
Bug report
BUG-REV-US · Priority: Critical (direct financial loss) · Reported by: finance
reverse(store, txId) returns the number of cents credited:
- unknown txId -> 0, nothing changes
- tx.debited is false (declined / failed before settlement: the customer never lost the money) -> 0, nothing changes (tx.refunded stays as it was)
- tx.refunded already true -> 0, nothing changes
- otherwise credit store.accounts[tx.account] with tx.amount, set tx.refunded = true, and return tx.amount
Observed: declined transactions are "refunded" — the customer's balance goes up by money they never spent, and the same reversal pays out again when the network resends it.
Logs
[card-us] tx=tx_991 status=DECLINED debited=false
[reversal] tx_991 credited 50000 to acct_17
[recon] ledger short by 50000 against settlement fileThe code as shipped
src/payments/reversal.js (editable)
// Handles a reversal notice from the card network.
exports.reverse = function (store, txId) {
var tx = store.txs[txId];
if (!tx) return 0;
store.accounts[tx.account] += tx.amount;
tx.refunded = true;
return tx.amount;
};
Read-only context: src/payments/store.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.