Repair a balance check that races

Two simultaneous purchases both see the same balance and spend more credit than the account owns. Replace the read-then-write sequence with the supplied atomic debit.

Requires Node.js 22 or newer. No package installation or server needed. To work in a container instead, open the folder with VS Code Dev Containers or GitHub Codespaces: .devcontainer/devcontainer.json uses the Dev Containers Node.js 24 image, pinned by digest.
1. Unzip all files into one folder.
2. In that folder, run: node --test exercise.test.mjs
3. Edit checkout.mjs, then run the same command again.

The starter deliberately fails some tests. A successful repair passes every case, including cases that catch common wrong fixes. The browser runs this same cases.mjs with the same fixtures; locally Node runs your code directly. If you completed the optional first stage in the browser, Download my work adds your input as stage1-input.json and the tests include it. Only run code you wrote or reviewed.

1. Reproduce overspending
Run the simultaneous-purchase case: balance 100, two purchases of 80. Both starter calls read 100 before either writes; the final balance becomes -60.

2. Validate money input
Reject zero, negative, fractional and unsafe integer amounts. Numeric strings are not valid amounts for this interface.

3. Move the invariant
Remove the separate read. Await store.debitIfEnough(amount) and translate its boolean result to paid or declined.

4. Check both outcomes
A concurrent pair must pay once and retain balance 20. A legitimate pair of 40-cent purchases must both succeed and retain balance 20.

SUPPLIED CONTRACT
purchase(store, amount) returns paid or declined. Reject amounts unless they are positive safe integers in cents, before calling any store operation. store.read() is asynchronous. store.debitUnsafe(amount) subtracts without checking. await store.debitIfEnough(amount) checks and subtracts as one operation, returning true or false. Its implementation is supplied by the harness. The unsafe-integer fixture also counts store calls, so declining solely because the balance is too small does not satisfy input validation.

REFERENCE
reference.mjs contains an explained solution: The unsafe schedule reads 100 twice, then subtracts 80 twice: final balance is -60. The atomic operation lets one 80-cent request pay, declines the other and leaves 20. The report sorts the two answers, so declined/paid does not say which caller won. Two 40-cent requests both pay and also leave 20. Rejecting negative, zero, fractional, string or unsafe-integer amounts before debit prevents the handler from creating credit or losing numeric precision. The supplied primitive owns atomicity; this workshop does not implement a database transaction.

SCOPE
The shared store models an atomic database operation by checking and subtracting without an intervening await. It does not establish isolation of a real database or payment provider. The linked transaction guide explains the database invariant and the separate runnable checkout project supplies a SQLite exercise. Production payments also require idempotency, account authorization and recovery.

NEXT TASK
Which database constraint, conditional update or locked transaction enforces your real balance invariant?

SOURCE
https://owasp.org/www-project-web-security-testing-guide/
