A wallet that takes consistency seriously.
What happens to money when withdrawals compete, deposits overlap and clients retry?
The problem
A deposit or withdrawal is not just an arithmetic operation. Two requests can read the same balance, a client can retry after a lost response, and every third deposit must award a bonus exactly once. A correct single-request path is not enough.
The constraints
Keep real money separate from bonus money. Withdraw only from the real balance. Represent amounts as integer cents rather than floating-point values, and retain an immutable record of each accepted operation.
The assignment uses PHP, Symfony and MySQL through plain PDO. The scope is a local internal API, not a regulated financial platform.
The decision
Start a database transaction and lock the customer row with SELECT … FOR UPDATE before checking the idempotency key or reading the balance. Save the updated balance and its operation record together, then commit.
Requests for one customer serialize at that boundary; different customers do not share that row lock. A unique customer-and-key constraint backs idempotency. Repeating the same operation returns its original result; reusing the key for a different operation is a conflict.
This deliberately favours a small, explicit transaction boundary over distributed coordination. A highly active customer can still become a contention point.
How the behaviour is checked
The repository documents integration tests that run operations in separate PHP processes with separate MySQL connections. A held customer lock forces them to compete before it is released.
The documented scenarios cover competing withdrawals, simultaneous deposits at the bonus boundary, a deposit alongside a withdrawal, and repeated deposits sharing one idempotency key. Acceptance tests also validate requests and responses against the OpenAPI contract.
These are repository-backed verification methods, not a fresh test run or a production benchmark performed by this website.
Read the design and concurrency tests ↗What this does not prove
This is a technical assignment, not a live banking product. The API has no authentication by design. It does not demonstrate regulatory compliance, production throughput, disaster recovery or an operational service-level commitment.
Correctness under the tested schedules does not remove the need to measure real contention and failure recovery before deployment.
Evidence links use a fixed repository revision so the referenced reasoning stays inspectable.
Explore the current repository ↗